Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] master from gatsbyjs:master #425

Merged
merged 6 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 94 additions & 3 deletions docs/docs/reference/built-in-components/gatsby-plugin-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Gatsby Image plugin
---

This guide will show you how to configure your images, including choosing layouts, placeholders and image processing options.
This guide will show you how to configure your images, including choosing layouts, placeholders and image processing options. While most of these options are available regardless of where you source your images, be sure to refer to the documentation of your source plugin if you are using images from a CMS, as the exact options are likely to vary.

## Components

Expand Down Expand Up @@ -106,9 +106,12 @@ This component accepts all [shared props](#shared-props), as well as the one bel
There are a few differences between how you specify options for `StaticImage` and `GatsbyImage`:

1. **How to pass options:** When using `StaticImage`, options are passed as props to the component, whereas for the `GatsbyImage` component they are passed to the `gatsbyImageData` GraphQL resolver.
1. **Option values:** In the `StaticImage` component, props such as `layout` and `placeholder` take a _string_, while the resolver takes a _[a GraphQL enum](https://graphql.org/learn/schema/#enumeration-types)_, which is in upper case by convention and is not quoted like a string. Both syntaxes are shown in the reference below.

**Note:** It is a very good idea to use [the GraphiQL IDE](/docs/how-to/querying-data/running-queries-with-graphiql) when writing your `gatsbyImageData` queries. It includes auto-complete and inline documentation for all of the options and lets you see the generated image data right inside the IDE.
2. **Option values:** In the `StaticImage` component, props such as `layout` and `placeholder` take a _string_, while the resolver takes a _[a GraphQL enum](https://graphql.org/learn/schema/#enumeration-types)_, which is upper case by convention and is not quoted like a string. Both syntaxes are shown in the reference below.

**Important:** For dynamic images, these options are for the `gatsbyImageData` resolver on [sharp nodes](https://www.gatsbyjs.com/plugins/gatsby-transformer-sharp). If you are using `gatsbyImageData` from a different plugin, such as a CMS or image host, you should refer to that plugin's documentation for the options, as they will differ from these. Static images use sharp under the hood, so these options apply when using the `StaticImage` component too.

It is a very good idea to use [the GraphiQL IDE](/docs/how-to/querying-data/running-queries-with-graphiql) when writing your `gatsbyImageData` queries. It includes auto-complete and inline documentation for all of the options and lets you see the generated image data right inside the browser.

Both static and dynamic images have the following options available:

Expand Down Expand Up @@ -236,3 +239,91 @@ The Gatsby Image plugin uses [sharp](https://sharp.pixelplumbing.org) for image
| `pngOptions` | None | Options to pass to sharp when generating PNG images. |
| `webpOptions` | None | Options to pass to sharp when generating WebP images. |
| `avifOptions` | None | Options to pass to sharp when generating AVIF images. |

## Helper functions

There are a number of utility functions to help you work with `gatsbyImageData` objects. We stringly recommend that you do not try to access the internals of these objects directly, as the format could change.

### `getImage`

Safely get a `gatsbyImageData` object. It accepts several different sorts of objects, and is null-safe, returning `undefined` if the object passed, or any intermediate children are undefined.

If passed a `File` object, it will return `file?.childImageSharp?.gatsbyImageData`. If passed a node such as a `ContentfulAsset` that includes a `gatsbyImageData` field, it will return the `gatsbyImageData` object. If passed a `gatsbyImageData` object itself, it will return the same object.

```js
import { getImage } from "gatsby-plugin-image"

const image = getImage(data.avatar)

// This is the same as:

const image = data?.avatar?.childImageSharp?.gatsbyImageData
```

### `getSrc`

Get the default image `src` as a string. This will be the fallback, so usually jpg or png. Accepts the same types as `getImage`.

```jsx
import { getSrc } from "gatsby-plugin-image"
//...
const src = getSrc(data.hero)

return <meta property="og:image" content={src} />
```

### `getSrcSet`

Get the default image `srcset`. This will be the fallback, so usually jpg or png.

### `withArtDirection`

By default, the plugin displays different image resolutions at different screen sizes, but it also supports art direction, which is where a visually-different image is displayed at different sizes. This could include displaying a simplified logo or a tighter crop on a profile picture when viewing on a small screen. To do this, you can use the `withArtDirection` function. You need both images available from GraphQL, and you should be able to write a media query for each size.

The first argument is the default image. This is displayed when no media queries match, but it is also used to set the layout, size, placeholder and most other options. You then pass an array of "art directed images" which are objects with `media` and `image` values.

```jsx
import { GatsbyImage, getImage, withArtDirection } from "gatsby-plugin-image"

export function MyImage({ data }) {
const images = withArtDirection(getImage(data.largeImage), [
{
media: "(max-width: 1024px)",
image: getImage(data.smallImage),
},
])

return <GatsbyImage image={images} />
}
```

When the screen is less than 1024px wide, it will display `smallImage`. Otherwise, it will display `largeImage`.

The aspect ratio is set by the default image, and doesn't automatically change with the different sources. The way to handle this is to use CSS media queries. For example, you could use this CSS to change the size of the container in small images:

```css:title=style.css
@media screen and (max-width: 1024px) {
.art-directed {
width: 400px;
height: 300px;
}
}
```

You can then apply this using plain CSS, or the styling system of your choice. e.g.

```jsx
import { GatsbyImage, getImage, withArtDirection } from "gatsby-plugin-image"
import "./style.css"

export function MyImage({ data }) {
const images = withArtDirection(getImage(data.largeImage), [
{
media: "(max-width: 1024px)",
image: getImage(data.smallImage),
},
])

return <GatsbyImage className="art-directed" image={images} />
}
```
20 changes: 20 additions & 0 deletions packages/babel-preset-gatsby/src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,24 @@ describe(`babel-preset-gatsby`, () => {
}),
])
})

it(`Allows to configure react importSource`, () => {
const { presets } = preset(null, {
reactImportSource: `@emotion/react`,
reactRuntime: `automatic`,
})

expect(presets[1]).toEqual([
expect.stringContaining(path.join(`@babel`, `preset-react`)),
expect.objectContaining({
importSource: `@emotion/react`,
}),
])
})

it(`Fails to configure react importSource if source is classic`, () => {
expect(() => preset(null, { reactImportSource: `@emotion/react` })).toThrow(
`@babel/preset-react\` requires reactRuntime \`automatic\` in order to use \`importSource\`.`
)
})
})
9 changes: 8 additions & 1 deletion packages/babel-preset-gatsby/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function loadCachedConfig() {
}

export default function preset(_, options = {}) {
let { targets = null } = options
let { targets = null, reactImportSource = null } = options

const stage = options.stage || `test`
const pluginBabelConfig = loadCachedConfig()
Expand All @@ -54,6 +54,12 @@ export default function preset(_, options = {}) {
}
}

if (reactImportSource && options.reactRuntime !== `automatic`) {
throw Error(
`\`@babel/preset-react\` requires reactRuntime \`automatic\` in order to use \`importSource\`.`
)
}

return {
presets: [
[
Expand Down Expand Up @@ -87,6 +93,7 @@ export default function preset(_, options = {}) {
: `React.createElement`,
development: stage === `develop`,
runtime: options.reactRuntime || `classic`,
...(reactImportSource && { importSource: reactImportSource }),
},
],
],
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby-plugin-gatsby-cloud/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "gatsby-plugin-gatsby-cloud",
"description": "A Gatsby plugin which optimizes working with Gatsby Cloud",
"version": "1.1.0-next.3",
"version": "2.0.0-next.0",
"author": "Kyle Mathews <mathews.kyle@gmail.com>",
"bugs": {
"url": "https://github.com/gatsbyjs/gatsby/issues"
Expand Down
11 changes: 5 additions & 6 deletions packages/gatsby/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,16 @@
"detect-port": "^1.3.0",
"devcert": "^1.1.3",
"dotenv": "^8.2.0",
"eslint": "^6.8.0",
"eslint": "^7.0.0",
"eslint-config-react-app": "^5.2.1",
"eslint-loader": "^4.0.2",
"eslint-plugin-flowtype": "^3.13.0",
"eslint-plugin-graphql": "^4.0.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-react": "^7.20.6",
"eslint-plugin-react-hooks": "^1.7.0",
"event-source-polyfill": "^1.0.15",
"eslint-webpack-plugin": "^2.5.2",
"execa": "^4.0.3",
"express": "^4.17.1",
"express-graphql": "^0.9.0",
Expand Down Expand Up @@ -138,8 +138,8 @@
"shallow-compare": "^1.2.2",
"signal-exit": "^3.0.3",
"slugify": "^1.4.4",
"socket.io": "2.3.0",
"socket.io-client": "2.3.0",
"socket.io": "3.1.1",
"socket.io-client": "3.1.1",
"source-map": "^0.7.3",
"source-map-support": "^0.5.19",
"st": "^2.0.0",
Expand Down Expand Up @@ -175,7 +175,6 @@
"@types/reach__router": "^1.3.5",
"@types/semver": "^7.1.0",
"@types/signal-exit": "^3.0.0",
"@types/socket.io": "^2.1.4",
"@types/string-similarity": "^3.0.0",
"@types/tmp": "^0.2.0",
"@types/webpack-virtual-modules": "^0.1.0",
Expand Down Expand Up @@ -261,4 +260,4 @@
"yargs": {
"boolean-negation": false
}
}
}
10 changes: 8 additions & 2 deletions packages/gatsby/src/commands/develop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import execa from "execa"
import chokidar from "chokidar"
import getRandomPort from "detect-port"
import { detectPortInUseAndPrompt } from "../utils/detect-port-in-use-and-prompt"
import socket from "socket.io"
import { Server as SocketIO } from "socket.io"
import fs from "fs-extra"
import onExit from "signal-exit"
import {
Expand Down Expand Up @@ -344,7 +344,13 @@ module.exports = async (program: IProgram): Promise<void> => {
: http.createServer()
statusServer.listen(statusServerPort)

const io = socket(statusServer)
const io = new SocketIO(statusServer, {
// whitelist all (https://github.com/expressjs/cors#configuration-options)
cors: {
origin: true,
},
cookie: true,
})

const handleChildProcessIPC = (msg): void => {
if (msg.type === `HEARTBEAT`) return
Expand Down
33 changes: 21 additions & 12 deletions packages/gatsby/src/utils/__tests__/websocket-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ jest.mock(`fs-extra`, () => {
}
})

// we mock it to make tests faster
jest.mock(`gatsby-cli/lib/reporter`, () => {
return {}
})

const INTERVAL_TIMEOUT = 500
const TEST_TIMEOUT = 30000

Expand Down Expand Up @@ -49,9 +54,13 @@ describe(`websocket-manager`, () => {
let websocketManager: WebsocketManager
let httpServerAddr

async function getClientSocket(): Promise<typeof io.Socket> {
function getClientSocket(): typeof io.Socket {
return io.default(`http://127.0.0.1:${httpServerAddr.port}`)
}

function getClientSocketAndWaitForConnect(): Promise<typeof io.Socket> {
return new Promise(resolve => {
const clientSocket = io.default(`http://127.0.0.1:${httpServerAddr.port}`)
const clientSocket = getClientSocket()
clientSocket.on(`connect`, () => {
resolve(clientSocket)
})
Expand Down Expand Up @@ -149,7 +158,7 @@ describe(`websocket-manager`, () => {
`Can connect`,
async () => {
expect.assertions(1)
const clientSocket = await getClientSocket()
const clientSocket = await getClientSocketAndWaitForConnect()
expect(clientSocket.connected).toBe(true)
clientSocket.disconnect()
},
Expand All @@ -170,7 +179,7 @@ describe(`websocket-manager`, () => {
async () => {
expect.assertions(3)

const clientSocket = await getClientSocket()
const clientSocket = await getClientSocketAndWaitForConnect()

expect(websocketManager.activePaths).toEqual(new Set())

Expand Down Expand Up @@ -202,8 +211,8 @@ describe(`websocket-manager`, () => {
`track individual clients`,
async () => {
expect.assertions(5)
const clientSocket1 = await getClientSocket()
const clientSocket2 = await getClientSocket()
const clientSocket1 = await getClientSocketAndWaitForConnect()
const clientSocket2 = await getClientSocketAndWaitForConnect()
expect(websocketManager.activePaths).toEqual(new Set())

let activePathAdjustedPromise = waitUntil(done => {
Expand Down Expand Up @@ -261,7 +270,7 @@ describe(`websocket-manager`, () => {
async function registerPathnameAndGetPath(
pathname: string
): Promise<string> {
const clientSocket = await getClientSocket()
const clientSocket = await getClientSocketAndWaitForConnect()

if (websocketManager.activePaths.size > 0) {
throw new Error(`There was client connected already`)
Expand Down Expand Up @@ -465,7 +474,7 @@ describe(`websocket-manager`, () => {
`Client can receive page query update`,
async () => {
expect.assertions(1)
const clientSocket = await getClientSocket()
const clientSocket = await getClientSocketAndWaitForConnect()

const pageQueryId = `/blog/`
const result = {
Expand Down Expand Up @@ -512,7 +521,7 @@ describe(`websocket-manager`, () => {
`Client can receive static query update`,
async () => {
expect.assertions(1)
const clientSocket = await getClientSocket()
const clientSocket = await getClientSocketAndWaitForConnect()

const staticQueryId = `12345`
const result = {
Expand Down Expand Up @@ -553,7 +562,7 @@ describe(`websocket-manager`, () => {
it(`Emits errors to display by clients`, async done => {
expect.assertions(1)

const clientSocket = await getClientSocket()
const clientSocket = await getClientSocketAndWaitForConnect()

function handler(msg): void {
if (
Expand All @@ -575,7 +584,7 @@ describe(`websocket-manager`, () => {
it(`Emits stored errors to new clients`, async done => {
expect.assertions(1)

const clientSocket = await getClientSocket()
const clientSocket = getClientSocket()

function handler(msg): void {
if (
Expand All @@ -597,7 +606,7 @@ describe(`websocket-manager`, () => {
it(`Can clear errors by emitting empty "overlayError" msg`, async done => {
expect.assertions(1)

const clientSocket = await getClientSocket()
const clientSocket = await getClientSocketAndWaitForConnect()

function handler(msg): void {
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,10 @@
import { store } from "../redux"
import { eslintConfig } from "./eslint-config"
import { hasLocalEslint } from "./local-eslint-config-finder"
import { RuleSetRule, Compiler, RuleSetQuery } from "webpack"
import { Compiler } from "webpack"
import { GraphQLSchema } from "graphql"
import { reactHasJsxRuntime } from "./webpack-utils"

function isEslintRule(rule?: RuleSetRule): boolean {
const options = rule?.use?.[0]?.options
return options && typeof options.useEslintrc !== `undefined`
}

import ESLintPlugin from "eslint-webpack-plugin"
export class GatsbyWebpackEslintGraphqlSchemaReload {
private plugin: { name: string }
private schema: GraphQLSchema | null
Expand All @@ -25,10 +20,11 @@ export class GatsbyWebpackEslintGraphqlSchemaReload {
this.schema = null
}

findEslintOptions(compiler: Compiler): RuleSetQuery | undefined {
const rules = compiler.options.module?.rules.find(isEslintRule)?.use
const rule = Array.isArray(rules) ? rules[0] : rules
return typeof rule === `object` ? rule?.options : undefined
findEslintOptions(compiler: Compiler): any | undefined {
const plugin = compiler.options.plugins.find(
item => item instanceof ESLintPlugin
)
return typeof plugin === `object` ? plugin?.options : undefined
}

apply(compiler: Compiler): void {
Expand Down
Loading