From 4814d607592006bb5e6bb054b5ffee0ab73f3f79 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 16 Sep 2020 19:29:14 +0100 Subject: [PATCH 001/143] Add static image plugin --- packages/gatsby-plugin-static-image/.babelrc | 3 + packages/gatsby-plugin-static-image/README.md | 205 ++++++++++ .../gatsby-plugin-static-image/gatsby-node.js | 2 + packages/gatsby-plugin-static-image/index.js | 2 + .../gatsby-plugin-static-image/package.json | 44 +++ .../src/babel-parse-to-ast.ts | 64 ++++ .../src/babel-plugin-parse-static-images.ts | 112 ++++++ .../src/gatsby-node.ts | 24 ++ .../src/image-processing.ts | 51 +++ .../gatsby-plugin-static-image/src/index.ts | 1 + .../gatsby-plugin-static-image/src/parser.ts | 28 ++ .../src/preprocess-source.ts | 45 +++ .../src/static-image.tsx | 28 ++ .../gatsby-plugin-static-image/src/types.d.ts | 4 + .../gatsby-plugin-static-image/src/utils.ts | 274 ++++++++++++++ .../gatsby-plugin-static-image/tsconfig.json | 4 + yarn.lock | 351 +++++++++++++++++- 17 files changed, 1239 insertions(+), 3 deletions(-) create mode 100644 packages/gatsby-plugin-static-image/.babelrc create mode 100644 packages/gatsby-plugin-static-image/README.md create mode 100644 packages/gatsby-plugin-static-image/gatsby-node.js create mode 100644 packages/gatsby-plugin-static-image/index.js create mode 100644 packages/gatsby-plugin-static-image/package.json create mode 100644 packages/gatsby-plugin-static-image/src/babel-parse-to-ast.ts create mode 100644 packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts create mode 100644 packages/gatsby-plugin-static-image/src/gatsby-node.ts create mode 100644 packages/gatsby-plugin-static-image/src/image-processing.ts create mode 100644 packages/gatsby-plugin-static-image/src/index.ts create mode 100644 packages/gatsby-plugin-static-image/src/parser.ts create mode 100644 packages/gatsby-plugin-static-image/src/preprocess-source.ts create mode 100644 packages/gatsby-plugin-static-image/src/static-image.tsx create mode 100644 packages/gatsby-plugin-static-image/src/types.d.ts create mode 100644 packages/gatsby-plugin-static-image/src/utils.ts create mode 100644 packages/gatsby-plugin-static-image/tsconfig.json diff --git a/packages/gatsby-plugin-static-image/.babelrc b/packages/gatsby-plugin-static-image/.babelrc new file mode 100644 index 0000000000000..ac0ad292bb087 --- /dev/null +++ b/packages/gatsby-plugin-static-image/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["babel-preset-gatsby-package"]] +} diff --git a/packages/gatsby-plugin-static-image/README.md b/packages/gatsby-plugin-static-image/README.md new file mode 100644 index 0000000000000..f78016e2b2720 --- /dev/null +++ b/packages/gatsby-plugin-static-image/README.md @@ -0,0 +1,205 @@ +# Experimental static images for Gatsby + +The [gatsby-image](https://www.gatsbyjs.org/packages/gatsby-image/), component combined with the sharp plugin, as an awesome way to automatically resize and optimise your images and serve them on the most performant way. This plugin is a proof of concept for a simpler way to use Gatsby's image processing tools without needing to write GraphQL queries. It is designed for static images such as logos rather than ones loaded dynamically from a CMS. + +The normal way to do this now is with `useStaticQuery`: + +```js +import React from "react" +import Img from "gatsby-image" + +export const Dino = () => { + const data = useStaticQuery(graphql` + query LogoQuery { + file(relativePath: { eq: "trex.png" }) { + childImageSharp { + fixed(height: 100) { + ...GatsbyImageSharpFixed + } + } + } + } + `) + + return T-Rex +} +``` + +This component lets you write this instead: + +```js +import React from "react" +import { StaticImage as Img } from "gatsby-plugin-static-image" + +export const Dino = () => T-Rex +``` + +The `src` prop is an image `relativePath`, so you need to ensure it's in a folder that is parsed by gatsby-source-filesystem. + +You can pass in options that match ones passed to the `ImageSharp` query: + +```js +import React from "react" +import { StaticImage as Img } from "gatsby-plugin-static-image" + +export const Dino = () => ( + T-Rex +) +``` + +...is equivalent to: + +```js +import React from "react" +import Img from "gatsby-image" + +export const Dino = () => { + const data = useStaticQuery(graphql` + query LogoQuery { + file(relativePath: { eq: "trex.png" }) { + childImageSharp { + fluid(maxWidth: 200, grayscale: true) { + ...GatsbyImageSharpFixed_withWebp_noBase64 + } + } + } + } + `) + + return T-Rex +} +``` + +## How does it work? + +When your site is compiled, any references to StaticImage components are extracted, the images are resized by Sharp in a similar way to `gatsby-transformer-sharp`, and then the resulting sharp object is written to `.cache/caches/gatsby-plugin-static-image/`, with the filename generated as a hash of the normalized image props. Next, a Babel plugin finds any references to StaticImage, calculates the same hash, loads the JSON file with the sharp object, then adds it as a new `parsedValues` prop. It then returns a GatsbyImage, passing the parsedValues as the fixed or fluid prop. + +### Are there restrictions to how this is used? + +You cannot pass variables or expressions to the props: they must be literal values, and not spread. For example, this is forbidden: + +```js +//Doesn't work +({ logo }) => +``` + +...and nor does this: + +```js +//Doesn't work +() => { + const width = 200; + return +} +``` + +## Installation + +```bash +npm install gatsby-plugin-static-image +``` + +...then add it to your `gatsby-config.js`: + +```js +module.exports = { + //... + plugins: [ + "gatsby-plugin-static-image", + //... + ], +} +``` + +### API + +The only required prop is `src`. The default type is `fixed`. + +For now: + +```typescript +export interface ImageOptions { + fixed?: boolean //Default true + fluid?: boolean + webP?: boolean + base64?: boolean // Default true + tracedSVG?: boolean +} + +export interface SomeGatsbyImageProps { + fadeIn?: boolean + durationFadeIn?: number + title?: string + alt?: string + className?: string | object + critical?: boolean + crossOrigin?: string | boolean + style?: object + imgStyle?: object + placeholderStyle?: object + placeholderClassName?: string + backgroundColor?: string | boolean + onLoad?: () => void + onError?: (event: Event) => void + onStartLoad?: (param: { wasCached: boolean }) => void + Tag?: string + itemProp?: string + loading?: `auto` | `lazy` | `eager` + draggable?: boolean +} + +export interface CommonImageProps { + quality?: number + jpegQuality?: number + pngQuality?: number + webpQuality?: number + grayscale?: boolean + // Disabled for now because parsing objects is annoying. One day. + // duotone?: false | { highlight: string; shadow: string } + toFormat?: "NO_CHANGE" | "JPG" | "PNG" | "WEBP" + cropFocus?: + | "CENTER" + | "NORTH" + | "NORTHEAST" + | "EAST" + | "SOUTHEAST" + | "SOUTH" + | "SOUTHWEST" + | "WEST" + | "NORTHWEST" + | "ENTROPY" + | "ATTENTION" + pngCompressionSpeed?: number + rotate?: number +} + +export interface FluidImageProps extends CommonImageProps { + fluid?: true + fixed?: false + maxWidth?: number + maxHeight?: number + srcSetBreakpoints?: number[] + fit?: number + background?: number +} + +export interface FixedImageProps extends CommonImageProps { + fixed?: true + fluid?: false + width?: number + height?: number +} + +export type AllProps = ImageOptions & + FluidImageProps & + FixedImageProps & + SomeGatsbyImageProps & { src: string } +``` diff --git a/packages/gatsby-plugin-static-image/gatsby-node.js b/packages/gatsby-plugin-static-image/gatsby-node.js new file mode 100644 index 0000000000000..2a15db4534328 --- /dev/null +++ b/packages/gatsby-plugin-static-image/gatsby-node.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +module.exports = require("./dist/gatsby-node"); diff --git a/packages/gatsby-plugin-static-image/index.js b/packages/gatsby-plugin-static-image/index.js new file mode 100644 index 0000000000000..b29631f345310 --- /dev/null +++ b/packages/gatsby-plugin-static-image/index.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +module.exports = require("./dist/"); diff --git a/packages/gatsby-plugin-static-image/package.json b/packages/gatsby-plugin-static-image/package.json new file mode 100644 index 0000000000000..f33e730d981ec --- /dev/null +++ b/packages/gatsby-plugin-static-image/package.json @@ -0,0 +1,44 @@ +{ + "name": "gatsby-plugin-static-image", + "version": "1.0.0", + "scripts": { + "build": "babel src --out-dir dist/ --ignore \"**/__tests__\" --ignore \"**/__mocks__\" --extensions \".ts,.tsx\"", + "typegen": "tsc --emitDeclarationOnly --declaration --declarationDir dist/", + "prepare": "cross-env NODE_ENV=production npm run build && npm run typegen", + "watch": "babel -w src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts,.tsx\"" + }, + "files": [ + "dist", + "index.js", + "gatsby-node.js" + ], + "types": "dist/index.d.ts", + "devDependencies": { + "@types/node": "^14.10.2", + "@babel/core": "^7.8.7", + "@types/babel__core": "^7.1.6", + "@types/babel__traverse": "^7.0.9", + "@types/fs-extra": "^8.1.0", + "gatsby": "^2.24.62", + "gatsby-image": "^2.4.19", + "prettier": "^2.1.1" + }, + "peerDependencies": { + "gatsby": ">=2", + "gatsby-core-utils": "*", + "gatsby-image": "*", + "gatsby-plugin-sharp": "*", + "react": "*" + }, + "dependencies": { + "@babel/parser": "^7.8.7", + "@babel/traverse": "^7.8.6", + "babel-plugin-remove-graphql-queries": "^2.9.19", + "fs-extra": "^8.1.0", + "normalize-path": "^3.0.0" + }, + "main": "index.js", + "repository": "git@github.com:ascorbic/gatsby-plugin-static-image.git", + "author": "Matt Kane ", + "license": "MIT" +} diff --git a/packages/gatsby-plugin-static-image/src/babel-parse-to-ast.ts b/packages/gatsby-plugin-static-image/src/babel-parse-to-ast.ts new file mode 100644 index 0000000000000..e1b2131e81e8e --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/babel-parse-to-ast.ts @@ -0,0 +1,64 @@ +import { parse, ParserOptions } from "@babel/parser" +import babel from "@babel/core" + +const PARSER_OPTIONS: ParserOptions = { + allowImportExportEverywhere: true, + allowReturnOutsideFunction: true, + allowSuperOutsideMethod: true, + sourceType: `unambiguous`, + plugins: [ + `jsx`, + `flow`, + `doExpressions`, + `objectRestSpread`, + [ + `decorators`, + { + decoratorsBeforeExport: true, + }, + ], + `classProperties`, + `classPrivateProperties`, + `classPrivateMethods`, + `exportDefaultFrom`, + `exportNamespaceFrom`, + `asyncGenerators`, + `functionBind`, + `functionSent`, + `dynamicImport`, + `numericSeparator`, + `optionalChaining`, + `importMeta`, + `bigInt`, + `optionalCatchBinding`, + `throwExpressions`, + [ + `pipelineOperator`, + { + proposal: `minimal`, + }, + ], + `nullishCoalescingOperator`, + ], +} + +export function getBabelParserOptions(filePath: string): ParserOptions { + // Flow and TypeScript plugins can't be enabled simultaneously + if (/\.tsx?/.test(filePath)) { + const { plugins } = PARSER_OPTIONS + return { + ...PARSER_OPTIONS, + plugins: (plugins || []).map(plugin => + plugin === `flow` ? `typescript` : plugin + ), + } + } + return PARSER_OPTIONS +} + +export function babelParseToAst( + contents: string, + filePath: string +): babel.types.File { + return parse(contents, getBabelParserOptions(filePath)) +} diff --git a/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts b/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts new file mode 100644 index 0000000000000..a6774d1c373c8 --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts @@ -0,0 +1,112 @@ +import * as types from "@babel/types" +import { PluginObj } from "@babel/core" +import { hashOptions, parseAttributes } from "./utils" +import fs from "fs-extra" +import path from "path" +import { + StringLiteral, + NumericLiteral, + BooleanLiteral, + ArrayExpression, + TemplateLiteral, + ObjectProperty, +} from "@babel/types" + +type AttrType = + | StringLiteral + | NumericLiteral + | BooleanLiteral + | ArrayExpression + | TemplateLiteral + +export default function attrs({ + types: t, +}: { + types: typeof types +}): PluginObj { + const componentImport = `StaticImage` + let localName = componentImport + + function generateLiterals(val: Array): Array { + return val.map(generateLiteral).filter(Boolean) as Array + } + + function generateLiteral( + val + ): + | StringLiteral + | NumericLiteral + | BooleanLiteral + | ArrayExpression + | TemplateLiteral { + switch (typeof val) { + case `string`: + return t.stringLiteral(val) + + case `number`: + return t.numericLiteral(val) + + case `boolean`: + return t.booleanLiteral(val) + + case `object`: + if (Array.isArray(val)) { + return t.arrayExpression(generateLiterals(val)) + } + } + return t.templateLiteral( + [t.templateElement({ raw: JSON.stringify(val) })], + [] + ) + } + + function generateProperties([key, val]): ObjectProperty { + return t.objectProperty(t.stringLiteral(key), generateLiteral(val)) + } + + return { + visitor: { + ImportSpecifier(nodePath): void { + if (nodePath.node.imported.name === componentImport) { + localName = nodePath.node.local.name + } + }, + JSXOpeningElement(nodePath): void { + const { name } = nodePath.node + if (name.type === `JSXMemberExpression` || name.name !== localName) { + return + } + + const props = parseAttributes(nodePath.node.attributes) + + const hash = hashOptions(props) + + const cacheDir = (this.opts as any)?.cacheDir + + if (!cacheDir || !hash) { + console.warn(`Couldn't file cache file for some reason`) + } + + const filename = path.join(cacheDir, `${hash}.json`) + + const data = fs.readJSONSync(filename) + + if (!data) { + console.warn(`No image data found for file ${props.src}`) + } + + const expressions = Object.entries(data).map(generateProperties) + + const newProp = t.jsxAttribute( + t.jsxIdentifier(`parsedValues`), + + t.jsxExpressionContainer(t.objectExpression(expressions)) + ) + + nodePath.node.attributes.push(newProp) + + console.log(nodePath.node.attributes) + }, + }, + } +} diff --git a/packages/gatsby-plugin-static-image/src/gatsby-node.ts b/packages/gatsby-plugin-static-image/src/gatsby-node.ts new file mode 100644 index 0000000000000..1b1bd964b0f21 --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/gatsby-node.ts @@ -0,0 +1,24 @@ +export * from "./preprocess-source" +import { GatsbyNode } from "gatsby" +import path from "path" + +export const onCreateBabelConfig: GatsbyNode["onCreateBabelConfig"] = ({ + actions, + store, +}) => { + const root = store.getState().program.directory + + const cacheDir = path.join( + root, + `.cache`, + `caches`, + `gatsby-plugin-static-image` + ) + + actions.setBabelPlugin({ + name: require.resolve(`./babel-plugin-parse-static-images`), + options: { + cacheDir, + }, + }) +} diff --git a/packages/gatsby-plugin-static-image/src/image-processing.ts b/packages/gatsby-plugin-static-image/src/image-processing.ts new file mode 100644 index 0000000000000..4712966c309be --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/image-processing.ts @@ -0,0 +1,51 @@ +import { Node, Reporter, GatsbyCache } from "gatsby" +import { fluid as fluidSharp, fixed as fixedSharp } from "gatsby-plugin-sharp" +const fileCache = new Map() +import fs from "fs-extra" +import path from "path" + +export async function writeImages({ + images, + cacheDir, + files, + reporter, + cache, +}: { + images: Map> + cacheDir: string + files: Array + reporter: Reporter + cache: GatsbyCache +}): Promise { + const promises = [...images.entries()].map( + async ([hash, { src, fluid, fixed, ...args }]) => { + const isFixed = fixed ?? !fluid + + let file = fileCache.get(src as string) + if (!file) { + // Is there a more efficient way to search for a fileNode by relativePath? + file = files.find(node => node.relativePath === src) + } + if (!file) { + reporter.warn(`Image not found ${src}`) + return + } + const filename = path.join(cacheDir, `${hash}.json`) + try { + const options = { file, args, reporter, cache } + const data = await (isFixed ? fixedSharp(options) : fluidSharp(options)) + + if (data) { + await fs.writeJSON(filename, data) + } else { + console.log(`Could not process image`) + } + } catch (e) { + // TODO: Report errors properly + console.log(`Error processing image`, e) + } + } + ) + + return Promise.all(promises).then(() => {}) +} diff --git a/packages/gatsby-plugin-static-image/src/index.ts b/packages/gatsby-plugin-static-image/src/index.ts new file mode 100644 index 0000000000000..bebf9ca03f80d --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/index.ts @@ -0,0 +1 @@ +export { StaticImage } from "./static-image" diff --git a/packages/gatsby-plugin-static-image/src/parser.ts b/packages/gatsby-plugin-static-image/src/parser.ts new file mode 100644 index 0000000000000..f01d6208a3935 --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/parser.ts @@ -0,0 +1,28 @@ +import traverse from "@babel/traverse" +import { parseAttributes, hashOptions } from "./utils" + +export const extractStaticImageProps = ( + ast: babel.types.File +): Map> => { + const componentImport = `StaticImage` + let localName = componentImport + + const images: Map> = new Map() + + traverse(ast, { + ImportSpecifier(path) { + if (path.node.imported.name === componentImport) { + localName = path.node.local.name + } + }, + JSXOpeningElement(path) { + const { name } = path.node + if (name.type === `JSXMemberExpression` || name.name !== localName) { + return + } + const image = parseAttributes(path.node.attributes) + images.set(hashOptions(image), image) + }, + }) + return images +} diff --git a/packages/gatsby-plugin-static-image/src/preprocess-source.ts b/packages/gatsby-plugin-static-image/src/preprocess-source.ts new file mode 100644 index 0000000000000..d2595d4416b21 --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/preprocess-source.ts @@ -0,0 +1,45 @@ +import { PreprocessSourceArgs } from "gatsby" +import { babelParseToAst } from "./babel-parse-to-ast" +import path from "path" +import { extractStaticImageProps } from "./parser" +import { writeImages } from "./image-processing" +const extensions: Array = [`.js`, `.jsx`, `.tsx`] + +export async function preprocessSource({ + filename, + contents, + getNodesByType, + cache, + reporter, + store, +}: PreprocessSourceArgs): Promise { + console.log(`preprocess`) + if ( + !contents.includes(`StaticImage`) || + !contents.includes(`gatsby-plugin-static-image`) || + !extensions.includes(path.extname(filename)) + ) { + console.log(`not found in`, filename) + return contents + } + const root = store.getState().program.directory + + const cacheDir = path.join( + root, + `.cache`, + `caches`, + `gatsby-plugin-static-image` + ) + + console.log(`yes found in`, filename) + + const ast = babelParseToAst(contents, filename) + + const images = extractStaticImageProps(ast) + + const files = getNodesByType(`File`) + + await writeImages({ images, cache, reporter, files, cacheDir }) + + return contents +} diff --git a/packages/gatsby-plugin-static-image/src/static-image.tsx b/packages/gatsby-plugin-static-image/src/static-image.tsx new file mode 100644 index 0000000000000..aed2cb358a8c5 --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/static-image.tsx @@ -0,0 +1,28 @@ +import React from "react" +import { splitProps, AllProps } from "./utils" +import Image, { FluidObject, FixedObject } from "gatsby-image" + +// These values are added by Babel. Do not add them manually +interface IPrivateProps { + parsedValues?: FluidObject & FixedObject +} + +export const StaticImage: React.FC = ({ + src, + parsedValues, + fluid, + fixed, + ...props +}) => { + const isFixed = fixed ?? !fluid + + const { gatsbyImageProps } = splitProps({ src, ...props }) + if (parsedValues) { + const imageProps = isFixed + ? { fixed: parsedValues } + : { fluid: parsedValues } + + return + } + return

Not an image

+} diff --git a/packages/gatsby-plugin-static-image/src/types.d.ts b/packages/gatsby-plugin-static-image/src/types.d.ts new file mode 100644 index 0000000000000..ac6d5adeaba1c --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/types.d.ts @@ -0,0 +1,4 @@ +declare module "babel-plugin-remove-graphql-queries/murmur" +declare module "gatsby-plugin-sharp/plugin-options" +declare module "gatsby-plugin-sharp" +declare module "gatsby-cli/lib/reporter" diff --git a/packages/gatsby-plugin-static-image/src/utils.ts b/packages/gatsby-plugin-static-image/src/utils.ts new file mode 100644 index 0000000000000..14bbd3b66ef67 --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/utils.ts @@ -0,0 +1,274 @@ +import murmurhash from "babel-plugin-remove-graphql-queries/murmur" + +import { + Expression, + SpreadElement, + JSXEmptyExpression, + JSXAttribute, + JSXSpreadAttribute, + JSXIdentifier, + JSXNamespacedName, +} from "@babel/types" + +export function parseIdentifier( + identifier: JSXIdentifier | JSXNamespacedName +): string { + if (identifier.type === `JSXIdentifier`) { + return identifier.name + } + return parseIdentifier(identifier.name) +} + +export function parseJSXAttribute( + attribute: JSXAttribute | JSXSpreadAttribute +): unknown { + if (attribute.type === `JSXSpreadAttribute`) { + return undefined + } + if (!attribute.value) { + return true + } + + switch (attribute.value.type) { + case `StringLiteral`: + return attribute.value.value + + case `JSXExpressionContainer`: + return parseAttributeValue(attribute.value.expression) + + default: + console.warn( + `Invalid type ${attribute.value.type} for attribute ${parseIdentifier( + attribute.name + )}` + ) + return undefined + } +} + +export function parseAttributeValue( + value: Expression | SpreadElement | null | JSXEmptyExpression +): unknown { + if (value === null) { + return true + } + switch (value.type) { + case `StringLiteral`: + case `NumericLiteral`: + case `BooleanLiteral`: + return value.value + + case `TemplateLiteral`: + if (value.expressions.length || value.quasis.length !== 1) { + return null + } + return value.quasis[0].value.raw + + case `ArrayExpression`: + return value.elements.map(parseAttributeValue) + } + return undefined +} + +export const SHARP_ATTRIBUTES = new Set([ + `src`, + `quality`, + `jpegQuality`, + `pngQuality`, + `webpQuality`, + `grayscale`, + `toFormat`, + `cropFocus`, + `pngCompressionSpeed`, + `rotate`, + `fluid`, + `fixed`, + `maxWidth`, + `maxHeight`, + `srcSetBreakpoints`, + `fit`, + `background`, + `width`, + `height`, +]) + +export function parseAttributes( + attributes: Array +): Record { + return attributes.reduce((prev, attribute): Record => { + if (attribute.type == `JSXSpreadAttribute`) { + return prev + } + const identifier = parseIdentifier(attribute.name) + if (!SHARP_ATTRIBUTES.has(identifier)) { + return prev + } + prev[identifier] = parseJSXAttribute(attribute) + return prev + }, {} as Record) +} + +export function hashOptions(options: unknown): string { + return `${murmurhash(JSON.stringify(options))}` +} + +export function hashAttributes( + attributes: Array +): string { + return hashOptions(parseAttributes(attributes)) +} + +export interface ISomeGatsbyImageProps { + fadeIn?: boolean + durationFadeIn?: number + title?: string + alt?: string + className?: string | object + critical?: boolean + crossOrigin?: string | boolean + style?: object + imgStyle?: object + placeholderStyle?: object + placeholderClassName?: string + backgroundColor?: string | boolean + onLoad?: () => void + onError?: (event: Event) => void + onStartLoad?: (param: { wasCached: boolean }) => void + Tag?: string + itemProp?: string + loading?: `auto` | `lazy` | `eager` + draggable?: boolean +} + +export interface ICommonImageProps { + fixed?: boolean + fluid?: boolean + quality?: number + jpegQuality?: number + pngQuality?: number + webpQuality?: number + grayscale?: boolean + toFormat?: "NO_CHANGE" | "JPG" | "PNG" | "WEBP" + cropFocus?: + | "CENTER" + | "NORTH" + | "NORTHEAST" + | "EAST" + | "SOUTHEAST" + | "SOUTH" + | "SOUTHWEST" + | "WEST" + | "NORTHWEST" + | "ENTROPY" + | "ATTENTION" + pngCompressionSpeed?: number + rotate?: number +} + +export interface IFluidImageProps extends ICommonImageProps { + maxWidth?: number + maxHeight?: number + srcSetBreakpoints?: Array + fit?: number + background?: number +} + +export interface IFixedImageProps extends ICommonImageProps { + width?: number + height?: number +} + +export type ImageProps = IFluidImageProps | IFixedImageProps +export type AnyImageProps = (IFluidImageProps | IFixedImageProps) & + ICommonImageProps + +export type AllProps = IImageOptions & + IFluidImageProps & + IFixedImageProps & + ISomeGatsbyImageProps & { src: string } + +export interface IImageOptions { + webP?: boolean + base64?: boolean + tracedSVG?: boolean +} + +export const splitProps = ( + props: AllProps +): { + commonOptions: ICommonImageProps + fluidOptions: IFluidImageProps + fixedOptions: IFixedImageProps + isFluid: boolean + isFixed: boolean + imageOptions: IImageOptions + gatsbyImageProps: ISomeGatsbyImageProps + src: string +} => { + const { + fluid, + fixed, + quality, + jpegQuality, + pngQuality, + webpQuality, + grayscale, + toFormat, + cropFocus, + pngCompressionSpeed, + maxWidth, + maxHeight, + srcSetBreakpoints, + fit, + background, + width, + height, + webP, + base64, + tracedSVG, + src, + ...gatsbyImageProps + } = props + + const isFixed = fixed ?? true + const isFluid = isFixed ?? !fluid + + const commonOptions: ICommonImageProps = { + quality, + jpegQuality, + pngQuality, + webpQuality, + grayscale, + toFormat, + cropFocus, + pngCompressionSpeed, + } + + const fluidOptions: IFluidImageProps = { + fluid: isFluid, + maxWidth, + maxHeight, + srcSetBreakpoints, + fit, + background, + } + + const imageOptions: IImageOptions = { + webP, + base64, + tracedSVG, + } + + const fixedOptions: IFixedImageProps = { fixed: isFixed, width, height } + + return { + src, + commonOptions, + fluidOptions, + fixedOptions, + isFluid, + isFixed, + imageOptions, + gatsbyImageProps, + } +} diff --git a/packages/gatsby-plugin-static-image/tsconfig.json b/packages/gatsby-plugin-static-image/tsconfig.json new file mode 100644 index 0000000000000..dd788c21d6cab --- /dev/null +++ b/packages/gatsby-plugin-static-image/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "exclude": ["node_modules", "src/__tests__", "src/__mocks__", "dist"] +} diff --git a/yarn.lock b/yarn.lock index 6da600dc79ef2..7d2ea4b8698ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -94,7 +94,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.10.2", "@babel/core@^7.10.3", "@babel/core@^7.11.6", "@babel/core@^7.7.5", "@babel/core@^7.9.0": +"@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.10.2", "@babel/core@^7.10.3", "@babel/core@^7.11.6", "@babel/core@^7.7.5", "@babel/core@^7.8.7", "@babel/core@^7.9.0": version "7.11.6" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.6.tgz#3a9455dc7387ff1bac45770650bc13ba04a15651" integrity sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg== @@ -370,7 +370,7 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.11.5", "@babel/parser@^7.3.3", "@babel/parser@^7.7.0": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.11.5", "@babel/parser@^7.3.3", "@babel/parser@^7.7.0", "@babel/parser@^7.8.7": version "7.11.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== @@ -1213,7 +1213,7 @@ "@babel/parser" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.10.5", "@babel/traverse@^7.11.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.9.0": +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.10.5", "@babel/traverse@^7.11.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": version "7.11.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ== @@ -3403,6 +3403,17 @@ "@types/babel__template" "*" "@types/babel__traverse" "*" +"@types/babel__core@^7.1.6": + version "7.1.9" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d" + integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + "@types/babel__generator@*": version "7.0.2" resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc" @@ -3425,6 +3436,13 @@ dependencies: "@babel/types" "^7.3.0" +"@types/babel__traverse@^7.0.9": + version "7.0.14" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.14.tgz#e99da8c075d4fb098c774ba65dabf7dc9954bd13" + integrity sha512-8w9szzKs14ZtBVuP6Wn7nMLRJ0D6dfB0VEBEyRgxrZ/Ln49aNMykrghM2FaNn4FJRzNppCSa0Rv9pBRM5Xc3wg== + dependencies: + "@babel/types" "^7.3.0" + "@types/better-queue@^3.8.2": version "3.8.2" resolved "https://registry.yarnpkg.com/@types/better-queue/-/better-queue-3.8.2.tgz#911a86863c1dd89a42308e03ee8d25ab7f6bafa7" @@ -3737,6 +3755,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.30.tgz#3501e6f09b954de9c404671cefdbcc5d9d7c45f6" integrity sha512-sz9MF/zk6qVr3pAnM0BSQvYIBK44tS75QC5N+VbWSE4DjCV/pJ+UzCW/F+vVnl7TkOPcuwQureKNtSSwjBTaMg== +"@types/node@^14.10.2": + version "14.10.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.2.tgz#9b47a2c8e4dabd4db73b57e750b24af689600514" + integrity sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw== + "@types/node@^8.5.7": version "8.10.59" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.59.tgz#9e34261f30183f9777017a13d185dfac6b899e04" @@ -10844,6 +10867,60 @@ gather-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/gather-stream/-/gather-stream-1.0.0.tgz#b33994af457a8115700d410f317733cbe7a0904b" +gatsby-cli@^2.12.99: + version "2.12.99" + resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-2.12.99.tgz#50078a0afd09854a92ca7243997b498de7d331b7" + integrity sha512-LHPX8bRHye69LPS9OiLw9in2ypyEnsxcU2p1MiBEs542D7bGmNXvJW61vN1kcXB9t5kFs3Ka2LDJjSn+5LbhfQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@hapi/joi" "^15.1.1" + "@types/common-tags" "^1.8.0" + better-opn "^1.0.0" + chalk "^2.4.2" + clipboardy "^2.3.0" + common-tags "^1.8.0" + configstore "^5.0.1" + convert-hrtime "^3.0.0" + envinfo "^7.5.1" + execa "^3.4.0" + fs-exists-cached "^1.0.0" + fs-extra "^8.1.0" + gatsby-core-utils "^1.3.20" + gatsby-recipes "^0.2.27" + gatsby-telemetry "^1.3.35" + hosted-git-info "^3.0.4" + ink "^2.7.1" + ink-spinner "^3.1.0" + is-valid-path "^0.1.1" + lodash "^4.17.20" + meant "^1.0.1" + node-fetch "^2.6.0" + opentracing "^0.14.4" + pretty-error "^2.1.1" + progress "^2.0.3" + prompts "^2.3.2" + react "^16.8.0" + redux "^4.0.5" + resolve-cwd "^3.0.0" + semver "^7.3.2" + signal-exit "^3.0.3" + source-map "0.7.3" + stack-trace "^0.0.10" + strip-ansi "^5.2.0" + update-notifier "^4.1.0" + uuid "3.4.0" + yargs "^15.3.1" + yurnalist "^1.1.2" + +gatsby-image@^2.4.19: + version "2.4.19" + resolved "https://registry.yarnpkg.com/gatsby-image/-/gatsby-image-2.4.19.tgz#c501439ce7973bfb7c17217bc9e26aa6f90c6072" + integrity sha512-LThyQk3ylD8MrK/6TY46Cl8ihvWqIGf15gB6X4UphOxaU+3ZVy6mJUumFIt5ZxFEBhGoHI8lmymBItwMwcsqcg== + dependencies: + "@babel/runtime" "^7.11.2" + object-fit-images "^3.2.4" + prop-types "^15.7.2" + gatsby-interface@0.0.183: version "0.0.183" resolved "https://registry.yarnpkg.com/gatsby-interface/-/gatsby-interface-0.0.183.tgz#cc1c7a655edd623635458a632bbf39d0b11f0eb9" @@ -10904,6 +10981,269 @@ gatsby-plugin-webfonts@^1.1.3: postcss "^7.0.17" postcss-js "^2.0.1" +gatsby-recipes@^0.2.27: + version "0.2.27" + resolved "https://registry.yarnpkg.com/gatsby-recipes/-/gatsby-recipes-0.2.27.tgz#85b7d31b9f6707f3980d666766ca8a085b5868d2" + integrity sha512-UaLmM4/+yyzQ/LSBu5kp8SrGe5ebOoiG/GU4z7UmKyL/rFaMdHPbWgc779b/LvJZX0159WxTHugeyQqT6JIjlg== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.11.6" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.11.0" + "@babel/plugin-transform-react-jsx" "^7.10.4" + "@babel/standalone" "^7.11.6" + "@babel/template" "^7.10.4" + "@babel/types" "^7.11.5" + "@emotion/core" "^10.0.14" + "@emotion/styled" "^10.0.14" + "@graphql-tools/schema" "^6.0.14" + "@graphql-tools/utils" "^6.0.14" + "@hapi/hoek" "8.x.x" + "@hapi/joi" "^15.1.1" + "@mdx-js/mdx" "^2.0.0-next.4" + "@mdx-js/react" "^2.0.0-next.4" + "@mdx-js/runtime" "^2.0.0-next.4" + acorn "^7.2.0" + acorn-jsx "^5.2.0" + ansi-html "^0.0.7" + babel-plugin-remove-export-keywords "^1.6.5" + better-queue "^3.8.10" + chokidar "^3.4.2" + concurrently "^5.0.0" + contentful-management "^5.26.3" + cors "^2.8.5" + cross-fetch "^3.0.6" + debug "^4.1.1" + detect-port "^1.3.0" + dotenv "^8.2.0" + execa "^4.0.2" + express "^4.17.1" + express-graphql "^0.9.0" + flatted "^3.0.0" + formik "^2.0.8" + fs-extra "^8.1.0" + gatsby-core-utils "^1.3.20" + gatsby-interface "^0.0.166" + gatsby-telemetry "^1.3.35" + glob "^7.1.6" + graphql "^14.6.0" + graphql-compose "^6.3.8" + graphql-subscriptions "^1.1.0" + graphql-type-json "^0.3.2" + hicat "^0.7.0" + html-tag-names "^1.1.5" + ink-box "^1.0.0" + is-binary-path "^2.1.0" + is-url "^1.2.4" + jest-diff "^25.5.0" + lock "^1.0.0" + lodash "^4.17.20" + mitt "^1.2.0" + mkdirp "^0.5.1" + node-fetch "^2.5.0" + normalize.css "^8.0.1" + pkg-dir "^4.2.0" + prettier "^2.0.5" + prop-types "^15.6.1" + property-information "5.5.0" + react-circular-progressbar "^2.0.0" + react-icons "^3.0.1" + react-reconciler "^0.25.1" + remark-mdx "^2.0.0-next.4" + remark-mdxjs "^2.0.0-next.4" + remark-parse "^6.0.3" + remark-stringify "^8.1.0" + resolve-cwd "^3.0.0" + resolve-from "^5.0.0" + semver "^7.3.2" + single-trailing-newline "^1.0.0" + strip-ansi "^6.0.0" + style-to-object "^0.3.0" + subscriptions-transport-ws "^0.9.16" + svg-tag-names "^2.0.1" + unified "^8.4.2" + unist-util-remove "^2.0.0" + unist-util-visit "^2.0.2" + urql "^1.9.7" + uuid "3.4.0" + ws "^7.3.0" + xstate "^4.9.1" + yoga-layout-prebuilt "^1.9.6" + yup "^0.27.0" + +gatsby-telemetry@^1.3.35: + version "1.3.35" + resolved "https://registry.yarnpkg.com/gatsby-telemetry/-/gatsby-telemetry-1.3.35.tgz#e188b7dac1c6edb0b908a7f67bb12082d9dc79c5" + integrity sha512-MFMQl5KCOO6Xzlp2JMO4bRbsh1rjQDsbkJRZgYZB9izmPSK8AgNrHCjruxZC448ndtUfIVwjHnV+/K18XuPCHw== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.11.2" + "@turist/fetch" "^7.1.7" + "@turist/time" "^0.0.1" + async-retry-ng "^2.0.1" + boxen "^4.2.0" + configstore "^5.0.1" + envinfo "^7.7.3" + fs-extra "^8.1.0" + gatsby-core-utils "^1.3.20" + git-up "^4.0.2" + is-docker "^2.1.1" + lodash "^4.17.20" + node-fetch "^2.6.0" + uuid "3.4.0" + +gatsby@^2.24.62: + version "2.24.62" + resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-2.24.62.tgz#227680792a2f4792d085a9f3f9d22c431d94d26c" + integrity sha512-S/Xd1jmcMecUlS6IxOm3XaDNc4/LuhHcl5OLcyOBW3lNBxNmGkbh2KPGi7PyNjBdZX69nTDqZUIUXci90GYSkw== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/core" "^7.11.6" + "@babel/parser" "^7.11.5" + "@babel/runtime" "^7.11.2" + "@babel/traverse" "^7.11.5" + "@babel/types" "^7.11.5" + "@hapi/joi" "^15.1.1" + "@mikaelkristiansson/domready" "^1.0.10" + "@pieh/friendly-errors-webpack-plugin" "1.7.0-chalk-2" + "@pmmmwh/react-refresh-webpack-plugin" "^0.4.1" + "@reach/router" "^1.3.4" + "@types/http-proxy" "^1.17.4" + "@typescript-eslint/eslint-plugin" "^2.24.0" + "@typescript-eslint/parser" "^2.24.0" + address "1.1.2" + autoprefixer "^9.8.4" + axios "^0.19.2" + babel-core "7.0.0-bridge.0" + babel-eslint "^10.1.0" + babel-loader "^8.1.0" + babel-plugin-add-module-exports "^0.3.3" + babel-plugin-dynamic-import-node "^2.3.3" + babel-plugin-lodash "3.3.4" + babel-plugin-remove-graphql-queries "^2.9.19" + babel-preset-gatsby "^0.5.10" + better-opn "1.0.0" + better-queue "^3.8.10" + bluebird "^3.7.2" + body-parser "^1.19.0" + browserslist "^4.12.2" + cache-manager "^2.11.1" + cache-manager-fs-hash "^0.0.9" + chalk "^2.4.2" + chokidar "^3.4.2" + common-tags "^1.8.0" + compression "^1.7.4" + convert-hrtime "^3.0.0" + copyfiles "^2.3.0" + core-js "^3.6.5" + cors "^2.8.5" + css-loader "^1.0.1" + date-fns "^2.14.0" + debug "^3.2.6" + del "^5.1.0" + detect-port "^1.3.0" + devcert "^1.1.3" + dotenv "^8.2.0" + eslint "^6.8.0" + eslint-config-react-app "^5.2.1" + eslint-loader "^2.2.1" + eslint-plugin-flowtype "^3.13.0" + eslint-plugin-graphql "^3.1.1" + 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" + execa "^4.0.3" + express "^4.17.1" + express-graphql "^0.9.0" + fast-levenshtein "^2.0.6" + file-loader "^1.1.11" + find-cache-dir "^3.3.1" + fs-exists-cached "1.0.0" + fs-extra "^8.1.0" + gatsby-cli "^2.12.99" + gatsby-core-utils "^1.3.20" + gatsby-graphiql-explorer "^0.4.14" + gatsby-legacy-polyfills "^0.0.4" + gatsby-link "^2.4.14" + gatsby-plugin-page-creator "^2.3.28" + gatsby-plugin-typescript "^2.4.20" + gatsby-react-router-scroll "^3.0.13" + gatsby-telemetry "^1.3.35" + glob "^7.1.6" + got "8.3.2" + graphql "^14.6.0" + graphql-compose "^6.3.8" + graphql-playground-middleware-express "^1.7.18" + hasha "^5.2.0" + http-proxy "^1.18.1" + invariant "^2.2.4" + is-relative "^1.0.0" + is-relative-url "^3.0.0" + is-wsl "^2.2.0" + jest-worker "^24.9.0" + json-loader "^0.5.7" + json-stringify-safe "^5.0.1" + latest-version "5.1.0" + lodash "^4.17.20" + md5-file "^3.2.3" + meant "^1.0.1" + micromatch "^3.1.10" + mime "^2.4.6" + mini-css-extract-plugin "^0.8.2" + mitt "^1.2.0" + mkdirp "^0.5.1" + moment "^2.27.0" + name-all-modules-plugin "^1.0.1" + normalize-path "^3.0.0" + null-loader "^3.0.0" + opentracing "^0.14.4" + optimize-css-assets-webpack-plugin "^5.0.3" + p-defer "^3.0.0" + parseurl "^1.3.3" + physical-cpu-count "^2.0.0" + pnp-webpack-plugin "^1.6.4" + postcss-flexbugs-fixes "^4.2.1" + postcss-loader "^3.0.0" + prompts "^2.3.2" + prop-types "^15.7.2" + query-string "^6.13.1" + raw-loader "^0.5.1" + react-dev-utils "^4.2.3" + react-error-overlay "^3.0.0" + react-hot-loader "^4.12.21" + react-refresh "^0.7.0" + redux "^4.0.5" + redux-thunk "^2.3.0" + semver "^7.3.2" + 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" + st "^2.0.0" + stack-trace "^0.0.10" + string-similarity "^1.2.2" + style-loader "^0.23.1" + terser-webpack-plugin "^1.4.4" + tmp "^0.2.1" + "true-case-path" "^2.2.1" + type-of "^2.0.1" + url-loader "^1.1.2" + util.promisify "^1.0.1" + uuid "3.4.0" + v8-compile-cache "^1.1.2" + webpack "^4.44.1" + webpack-dev-middleware "^3.7.2" + webpack-dev-server "^3.11.0" + webpack-hot-middleware "^2.25.0" + webpack-merge "^4.2.2" + webpack-stats-plugin "^0.3.1" + webpack-virtual-modules "^0.2.2" + xstate "^4.11.0" + yaml-loader "^0.6.0" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -18968,6 +19308,11 @@ prettier@2.0.5, prettier@^2.0.5: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== +prettier@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" + integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== + pretty-bytes@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-3.0.1.tgz#27d0008d778063a0b4811bb35c79f1bd5d5fbccf" From 5509d90c4cb7817e47807587575067a688db0e7c Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 16 Sep 2020 19:58:56 +0100 Subject: [PATCH 002/143] Parse static image files --- packages/gatsby/src/query/file-parser.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/gatsby/src/query/file-parser.js b/packages/gatsby/src/query/file-parser.js index dbdca7456ea37..0f8db930ca874 100644 --- a/packages/gatsby/src/query/file-parser.js +++ b/packages/gatsby/src/query/file-parser.js @@ -418,7 +418,11 @@ export default class FileParser { return null } - if (!text.includes(`graphql`)) return null + if ( + !text.includes(`graphql`) && + !text.includes(`gatsby-plugin-static-image`) + ) + return null const hash = crypto .createHash(`md5`) .update(file) From 6585d84d31a1aa3eac29e4b7bc4c230a3e4f4dff Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 16 Sep 2020 20:08:12 +0100 Subject: [PATCH 003/143] Fix package.json --- .../gatsby-plugin-static-image/package.json | 16 +- yarn.lock | 324 +----------------- 2 files changed, 9 insertions(+), 331 deletions(-) diff --git a/packages/gatsby-plugin-static-image/package.json b/packages/gatsby-plugin-static-image/package.json index f33e730d981ec..1ec4a100abf00 100644 --- a/packages/gatsby-plugin-static-image/package.json +++ b/packages/gatsby-plugin-static-image/package.json @@ -18,10 +18,7 @@ "@babel/core": "^7.8.7", "@types/babel__core": "^7.1.6", "@types/babel__traverse": "^7.0.9", - "@types/fs-extra": "^8.1.0", - "gatsby": "^2.24.62", - "gatsby-image": "^2.4.19", - "prettier": "^2.1.1" + "@types/fs-extra": "^8.1.0" }, "peerDependencies": { "gatsby": ">=2", @@ -34,11 +31,14 @@ "@babel/parser": "^7.8.7", "@babel/traverse": "^7.8.6", "babel-plugin-remove-graphql-queries": "^2.9.19", - "fs-extra": "^8.1.0", - "normalize-path": "^3.0.0" + "fs-extra": "^8.1.0" }, "main": "index.js", - "repository": "git@github.com:ascorbic/gatsby-plugin-static-image.git", - "author": "Matt Kane ", + "repository": { + "type": "git", + "url": "https://github.com/gatsbyjs/gatsby.git", + "directory": "packages/gatsby-plugin-static-images" + }, + "author": "Matt Kane ", "license": "MIT" } diff --git a/yarn.lock b/yarn.lock index 7d2ea4b8698ea..a5b194c27a088 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5462,7 +5462,7 @@ babel-plugin-jest-hoist@^24.9.0: dependencies: "@types/babel__traverse" "^7.0.6" -babel-plugin-lodash@3.3.4, babel-plugin-lodash@^3.3.4: +babel-plugin-lodash@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/babel-plugin-lodash/-/babel-plugin-lodash-3.3.4.tgz#4f6844358a1340baed182adbeffa8df9967bc196" integrity sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg== @@ -10867,60 +10867,6 @@ gather-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/gather-stream/-/gather-stream-1.0.0.tgz#b33994af457a8115700d410f317733cbe7a0904b" -gatsby-cli@^2.12.99: - version "2.12.99" - resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-2.12.99.tgz#50078a0afd09854a92ca7243997b498de7d331b7" - integrity sha512-LHPX8bRHye69LPS9OiLw9in2ypyEnsxcU2p1MiBEs542D7bGmNXvJW61vN1kcXB9t5kFs3Ka2LDJjSn+5LbhfQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@hapi/joi" "^15.1.1" - "@types/common-tags" "^1.8.0" - better-opn "^1.0.0" - chalk "^2.4.2" - clipboardy "^2.3.0" - common-tags "^1.8.0" - configstore "^5.0.1" - convert-hrtime "^3.0.0" - envinfo "^7.5.1" - execa "^3.4.0" - fs-exists-cached "^1.0.0" - fs-extra "^8.1.0" - gatsby-core-utils "^1.3.20" - gatsby-recipes "^0.2.27" - gatsby-telemetry "^1.3.35" - hosted-git-info "^3.0.4" - ink "^2.7.1" - ink-spinner "^3.1.0" - is-valid-path "^0.1.1" - lodash "^4.17.20" - meant "^1.0.1" - node-fetch "^2.6.0" - opentracing "^0.14.4" - pretty-error "^2.1.1" - progress "^2.0.3" - prompts "^2.3.2" - react "^16.8.0" - redux "^4.0.5" - resolve-cwd "^3.0.0" - semver "^7.3.2" - signal-exit "^3.0.3" - source-map "0.7.3" - stack-trace "^0.0.10" - strip-ansi "^5.2.0" - update-notifier "^4.1.0" - uuid "3.4.0" - yargs "^15.3.1" - yurnalist "^1.1.2" - -gatsby-image@^2.4.19: - version "2.4.19" - resolved "https://registry.yarnpkg.com/gatsby-image/-/gatsby-image-2.4.19.tgz#c501439ce7973bfb7c17217bc9e26aa6f90c6072" - integrity sha512-LThyQk3ylD8MrK/6TY46Cl8ihvWqIGf15gB6X4UphOxaU+3ZVy6mJUumFIt5ZxFEBhGoHI8lmymBItwMwcsqcg== - dependencies: - "@babel/runtime" "^7.11.2" - object-fit-images "^3.2.4" - prop-types "^15.7.2" - gatsby-interface@0.0.183: version "0.0.183" resolved "https://registry.yarnpkg.com/gatsby-interface/-/gatsby-interface-0.0.183.tgz#cc1c7a655edd623635458a632bbf39d0b11f0eb9" @@ -10981,269 +10927,6 @@ gatsby-plugin-webfonts@^1.1.3: postcss "^7.0.17" postcss-js "^2.0.1" -gatsby-recipes@^0.2.27: - version "0.2.27" - resolved "https://registry.yarnpkg.com/gatsby-recipes/-/gatsby-recipes-0.2.27.tgz#85b7d31b9f6707f3980d666766ca8a085b5868d2" - integrity sha512-UaLmM4/+yyzQ/LSBu5kp8SrGe5ebOoiG/GU4z7UmKyL/rFaMdHPbWgc779b/LvJZX0159WxTHugeyQqT6JIjlg== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.11.6" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-proposal-optional-chaining" "^7.11.0" - "@babel/plugin-transform-react-jsx" "^7.10.4" - "@babel/standalone" "^7.11.6" - "@babel/template" "^7.10.4" - "@babel/types" "^7.11.5" - "@emotion/core" "^10.0.14" - "@emotion/styled" "^10.0.14" - "@graphql-tools/schema" "^6.0.14" - "@graphql-tools/utils" "^6.0.14" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "^15.1.1" - "@mdx-js/mdx" "^2.0.0-next.4" - "@mdx-js/react" "^2.0.0-next.4" - "@mdx-js/runtime" "^2.0.0-next.4" - acorn "^7.2.0" - acorn-jsx "^5.2.0" - ansi-html "^0.0.7" - babel-plugin-remove-export-keywords "^1.6.5" - better-queue "^3.8.10" - chokidar "^3.4.2" - concurrently "^5.0.0" - contentful-management "^5.26.3" - cors "^2.8.5" - cross-fetch "^3.0.6" - debug "^4.1.1" - detect-port "^1.3.0" - dotenv "^8.2.0" - execa "^4.0.2" - express "^4.17.1" - express-graphql "^0.9.0" - flatted "^3.0.0" - formik "^2.0.8" - fs-extra "^8.1.0" - gatsby-core-utils "^1.3.20" - gatsby-interface "^0.0.166" - gatsby-telemetry "^1.3.35" - glob "^7.1.6" - graphql "^14.6.0" - graphql-compose "^6.3.8" - graphql-subscriptions "^1.1.0" - graphql-type-json "^0.3.2" - hicat "^0.7.0" - html-tag-names "^1.1.5" - ink-box "^1.0.0" - is-binary-path "^2.1.0" - is-url "^1.2.4" - jest-diff "^25.5.0" - lock "^1.0.0" - lodash "^4.17.20" - mitt "^1.2.0" - mkdirp "^0.5.1" - node-fetch "^2.5.0" - normalize.css "^8.0.1" - pkg-dir "^4.2.0" - prettier "^2.0.5" - prop-types "^15.6.1" - property-information "5.5.0" - react-circular-progressbar "^2.0.0" - react-icons "^3.0.1" - react-reconciler "^0.25.1" - remark-mdx "^2.0.0-next.4" - remark-mdxjs "^2.0.0-next.4" - remark-parse "^6.0.3" - remark-stringify "^8.1.0" - resolve-cwd "^3.0.0" - resolve-from "^5.0.0" - semver "^7.3.2" - single-trailing-newline "^1.0.0" - strip-ansi "^6.0.0" - style-to-object "^0.3.0" - subscriptions-transport-ws "^0.9.16" - svg-tag-names "^2.0.1" - unified "^8.4.2" - unist-util-remove "^2.0.0" - unist-util-visit "^2.0.2" - urql "^1.9.7" - uuid "3.4.0" - ws "^7.3.0" - xstate "^4.9.1" - yoga-layout-prebuilt "^1.9.6" - yup "^0.27.0" - -gatsby-telemetry@^1.3.35: - version "1.3.35" - resolved "https://registry.yarnpkg.com/gatsby-telemetry/-/gatsby-telemetry-1.3.35.tgz#e188b7dac1c6edb0b908a7f67bb12082d9dc79c5" - integrity sha512-MFMQl5KCOO6Xzlp2JMO4bRbsh1rjQDsbkJRZgYZB9izmPSK8AgNrHCjruxZC448ndtUfIVwjHnV+/K18XuPCHw== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.11.2" - "@turist/fetch" "^7.1.7" - "@turist/time" "^0.0.1" - async-retry-ng "^2.0.1" - boxen "^4.2.0" - configstore "^5.0.1" - envinfo "^7.7.3" - fs-extra "^8.1.0" - gatsby-core-utils "^1.3.20" - git-up "^4.0.2" - is-docker "^2.1.1" - lodash "^4.17.20" - node-fetch "^2.6.0" - uuid "3.4.0" - -gatsby@^2.24.62: - version "2.24.62" - resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-2.24.62.tgz#227680792a2f4792d085a9f3f9d22c431d94d26c" - integrity sha512-S/Xd1jmcMecUlS6IxOm3XaDNc4/LuhHcl5OLcyOBW3lNBxNmGkbh2KPGi7PyNjBdZX69nTDqZUIUXci90GYSkw== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/core" "^7.11.6" - "@babel/parser" "^7.11.5" - "@babel/runtime" "^7.11.2" - "@babel/traverse" "^7.11.5" - "@babel/types" "^7.11.5" - "@hapi/joi" "^15.1.1" - "@mikaelkristiansson/domready" "^1.0.10" - "@pieh/friendly-errors-webpack-plugin" "1.7.0-chalk-2" - "@pmmmwh/react-refresh-webpack-plugin" "^0.4.1" - "@reach/router" "^1.3.4" - "@types/http-proxy" "^1.17.4" - "@typescript-eslint/eslint-plugin" "^2.24.0" - "@typescript-eslint/parser" "^2.24.0" - address "1.1.2" - autoprefixer "^9.8.4" - axios "^0.19.2" - babel-core "7.0.0-bridge.0" - babel-eslint "^10.1.0" - babel-loader "^8.1.0" - babel-plugin-add-module-exports "^0.3.3" - babel-plugin-dynamic-import-node "^2.3.3" - babel-plugin-lodash "3.3.4" - babel-plugin-remove-graphql-queries "^2.9.19" - babel-preset-gatsby "^0.5.10" - better-opn "1.0.0" - better-queue "^3.8.10" - bluebird "^3.7.2" - body-parser "^1.19.0" - browserslist "^4.12.2" - cache-manager "^2.11.1" - cache-manager-fs-hash "^0.0.9" - chalk "^2.4.2" - chokidar "^3.4.2" - common-tags "^1.8.0" - compression "^1.7.4" - convert-hrtime "^3.0.0" - copyfiles "^2.3.0" - core-js "^3.6.5" - cors "^2.8.5" - css-loader "^1.0.1" - date-fns "^2.14.0" - debug "^3.2.6" - del "^5.1.0" - detect-port "^1.3.0" - devcert "^1.1.3" - dotenv "^8.2.0" - eslint "^6.8.0" - eslint-config-react-app "^5.2.1" - eslint-loader "^2.2.1" - eslint-plugin-flowtype "^3.13.0" - eslint-plugin-graphql "^3.1.1" - 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" - execa "^4.0.3" - express "^4.17.1" - express-graphql "^0.9.0" - fast-levenshtein "^2.0.6" - file-loader "^1.1.11" - find-cache-dir "^3.3.1" - fs-exists-cached "1.0.0" - fs-extra "^8.1.0" - gatsby-cli "^2.12.99" - gatsby-core-utils "^1.3.20" - gatsby-graphiql-explorer "^0.4.14" - gatsby-legacy-polyfills "^0.0.4" - gatsby-link "^2.4.14" - gatsby-plugin-page-creator "^2.3.28" - gatsby-plugin-typescript "^2.4.20" - gatsby-react-router-scroll "^3.0.13" - gatsby-telemetry "^1.3.35" - glob "^7.1.6" - got "8.3.2" - graphql "^14.6.0" - graphql-compose "^6.3.8" - graphql-playground-middleware-express "^1.7.18" - hasha "^5.2.0" - http-proxy "^1.18.1" - invariant "^2.2.4" - is-relative "^1.0.0" - is-relative-url "^3.0.0" - is-wsl "^2.2.0" - jest-worker "^24.9.0" - json-loader "^0.5.7" - json-stringify-safe "^5.0.1" - latest-version "5.1.0" - lodash "^4.17.20" - md5-file "^3.2.3" - meant "^1.0.1" - micromatch "^3.1.10" - mime "^2.4.6" - mini-css-extract-plugin "^0.8.2" - mitt "^1.2.0" - mkdirp "^0.5.1" - moment "^2.27.0" - name-all-modules-plugin "^1.0.1" - normalize-path "^3.0.0" - null-loader "^3.0.0" - opentracing "^0.14.4" - optimize-css-assets-webpack-plugin "^5.0.3" - p-defer "^3.0.0" - parseurl "^1.3.3" - physical-cpu-count "^2.0.0" - pnp-webpack-plugin "^1.6.4" - postcss-flexbugs-fixes "^4.2.1" - postcss-loader "^3.0.0" - prompts "^2.3.2" - prop-types "^15.7.2" - query-string "^6.13.1" - raw-loader "^0.5.1" - react-dev-utils "^4.2.3" - react-error-overlay "^3.0.0" - react-hot-loader "^4.12.21" - react-refresh "^0.7.0" - redux "^4.0.5" - redux-thunk "^2.3.0" - semver "^7.3.2" - 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" - st "^2.0.0" - stack-trace "^0.0.10" - string-similarity "^1.2.2" - style-loader "^0.23.1" - terser-webpack-plugin "^1.4.4" - tmp "^0.2.1" - "true-case-path" "^2.2.1" - type-of "^2.0.1" - url-loader "^1.1.2" - util.promisify "^1.0.1" - uuid "3.4.0" - v8-compile-cache "^1.1.2" - webpack "^4.44.1" - webpack-dev-middleware "^3.7.2" - webpack-dev-server "^3.11.0" - webpack-hot-middleware "^2.25.0" - webpack-merge "^4.2.2" - webpack-stats-plugin "^0.3.1" - webpack-virtual-modules "^0.2.2" - xstate "^4.11.0" - yaml-loader "^0.6.0" - gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -19308,11 +18991,6 @@ prettier@2.0.5, prettier@^2.0.5: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== -prettier@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" - integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== - pretty-bytes@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-3.0.1.tgz#27d0008d778063a0b4811bb35c79f1bd5d5fbccf" From 83b032149ce87ee0555aaa3bf2dd4638607eb151 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 17 Sep 2020 16:44:06 +0100 Subject: [PATCH 004/143] Better static analysis --- .../src/babel-plugin-parse-static-images.ts | 63 ++++++++---- .../src/jsx-utils.ts | 74 ++++++++++++++ .../gatsby-plugin-static-image/src/parser.ts | 26 ++--- .../src/static-image.tsx | 8 +- .../gatsby-plugin-static-image/src/utils.ts | 98 ++----------------- 5 files changed, 145 insertions(+), 124 deletions(-) create mode 100644 packages/gatsby-plugin-static-image/src/jsx-utils.ts diff --git a/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts b/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts index a6774d1c373c8..42f8c4fbba8b4 100644 --- a/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts +++ b/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts @@ -1,6 +1,6 @@ import * as types from "@babel/types" import { PluginObj } from "@babel/core" -import { hashOptions, parseAttributes } from "./utils" +import { hashOptions, evaluateImageAttributes } from "./utils" import fs from "fs-extra" import path from "path" import { @@ -24,9 +24,6 @@ export default function attrs({ }: { types: typeof types }): PluginObj { - const componentImport = `StaticImage` - let localName = componentImport - function generateLiterals(val: Array): Array { return val.map(generateLiteral).filter(Boolean) as Array } @@ -66,33 +63,63 @@ export default function attrs({ return { visitor: { - ImportSpecifier(nodePath): void { - if (nodePath.node.imported.name === componentImport) { - localName = nodePath.node.local.name - } - }, JSXOpeningElement(nodePath): void { - const { name } = nodePath.node - if (name.type === `JSXMemberExpression` || name.name !== localName) { + if ( + !nodePath + .get(`name`) + .referencesImport(`gatsby-plugin-static-image`, `StaticImage`) + ) { return } - const props = parseAttributes(nodePath.node.attributes) + const errors: Array = [] + + const props = evaluateImageAttributes(nodePath, prop => { + errors.push(prop) + }) + + let error + + if (errors.length) { + error = `Could not find values for the following props at build time: ${errors.join()}` + console.warn(error) + } const hash = hashOptions(props) - const cacheDir = (this.opts as any)?.cacheDir + const cacheDir = (this.opts as Record)?.cacheDir if (!cacheDir || !hash) { - console.warn(`Couldn't file cache file for some reason`) + console.warn(`Couldn't find cache file for some reason`) } const filename = path.join(cacheDir, `${hash}.json`) - - const data = fs.readJSONSync(filename) + let data: Record | undefined + try { + data = fs.readJSONSync(filename) + } catch (e) { + console.warn(`Could not read file ${filename}`, e) + } if (!data) { - console.warn(`No image data found for file ${props.src}`) + console.warn(`No image data found for file ${props.src}`, error) + const newProp = t.jsxAttribute( + t.jsxIdentifier(`__error`), + + t.jsxExpressionContainer( + t.stringLiteral(`No image data found for file "${props.src}" + ${error || ``}`) + ) + ) + nodePath.node.attributes.push(newProp) + return + } + if (error) { + const newProp = t.jsxAttribute( + t.jsxIdentifier(`__error`), + t.stringLiteral(error) + ) + nodePath.node.attributes.push(newProp) } const expressions = Object.entries(data).map(generateProperties) @@ -104,8 +131,6 @@ export default function attrs({ ) nodePath.node.attributes.push(newProp) - - console.log(nodePath.node.attributes) }, }, } diff --git a/packages/gatsby-plugin-static-image/src/jsx-utils.ts b/packages/gatsby-plugin-static-image/src/jsx-utils.ts new file mode 100644 index 0000000000000..9c9a5baa6958a --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/jsx-utils.ts @@ -0,0 +1,74 @@ +import { + JSXAttribute, + JSXIdentifier, + JSXNamespacedName, + JSXOpeningElement, +} from "@babel/types" +import { NodePath } from "@babel/core" + +export function parseIdentifier( + identifier: JSXIdentifier | JSXNamespacedName +): string { + if (identifier.type === `JSXIdentifier`) { + return identifier.name + } + return parseIdentifier(identifier.name) +} + +export function evaluateAttributes( + nodePath: NodePath, + onError?: (prop: string) => void, + include?: Set +): Record { + let result = {} + + nodePath.traverse({ + JSXSpreadAttribute(attrPath) { + const spreadValues = attrPath.get(`argument`).evaluate() + if (spreadValues.confident) { + result = { ...result, ...spreadValues.value } + } else { + // eslint-disable-next-line no-unused-expressions + onError?.(``) + } + }, + JSXAttribute(attrPath) { + const prop = parseIdentifier(attrPath.node.name) + if (include && !include.has(prop)) { + return + } + const { value, confident } = evaluateAttributeValue(attrPath) + if (confident) { + result[prop] = value + } else { + // eslint-disable-next-line no-unused-expressions + onError?.(prop) + } + }, + }) + + return result +} + +export function evaluateAttributeValue( + nodePath: NodePath +): { + confident: boolean + value: unknown +} { + const valueNode = nodePath.get(`value`) + if (!valueNode.node) { + // empty attributes are truthy + return { confident: true, value: true } + } else if (valueNode.node.type === `JSXExpressionContainer`) { + const expression = valueNode.get(`expression`) + + if (Array.isArray(expression)) { + return expression[0]?.evaluate() + } + + return expression.evaluate() + } else { + return valueNode.evaluate() + } +} diff --git a/packages/gatsby-plugin-static-image/src/parser.ts b/packages/gatsby-plugin-static-image/src/parser.ts index f01d6208a3935..45de5f1a890ac 100644 --- a/packages/gatsby-plugin-static-image/src/parser.ts +++ b/packages/gatsby-plugin-static-image/src/parser.ts @@ -1,26 +1,26 @@ import traverse from "@babel/traverse" -import { parseAttributes, hashOptions } from "./utils" +import { hashOptions, evaluateImageAttributes } from "./utils" +import { NodePath } from "@babel/core" +import { JSXOpeningElement } from "@babel/types" export const extractStaticImageProps = ( ast: babel.types.File ): Map> => { - const componentImport = `StaticImage` - let localName = componentImport - const images: Map> = new Map() traverse(ast, { - ImportSpecifier(path) { - if (path.node.imported.name === componentImport) { - localName = path.node.local.name - } - }, - JSXOpeningElement(path) { - const { name } = path.node - if (name.type === `JSXMemberExpression` || name.name !== localName) { + JSXOpeningElement(nodePath) { + if ( + !nodePath + .get(`name`) + .referencesImport(`gatsby-plugin-static-image`, `StaticImage`) + ) { return } - const image = parseAttributes(path.node.attributes) + const image = evaluateImageAttributes( + // There's a conflict between the definition of NodePath in @babel/core and @babel/traverse + (nodePath as unknown) as NodePath + ) images.set(hashOptions(image), image) }, }) diff --git a/packages/gatsby-plugin-static-image/src/static-image.tsx b/packages/gatsby-plugin-static-image/src/static-image.tsx index aed2cb358a8c5..2e2fd58cbda4c 100644 --- a/packages/gatsby-plugin-static-image/src/static-image.tsx +++ b/packages/gatsby-plugin-static-image/src/static-image.tsx @@ -5,6 +5,7 @@ import Image, { FluidObject, FixedObject } from "gatsby-image" // These values are added by Babel. Do not add them manually interface IPrivateProps { parsedValues?: FluidObject & FixedObject + __error?: string } export const StaticImage: React.FC = ({ @@ -12,9 +13,13 @@ export const StaticImage: React.FC = ({ parsedValues, fluid, fixed, + __error, ...props }) => { const isFixed = fixed ?? !fluid + if (__error) { + console.warn(__error) + } const { gatsbyImageProps } = splitProps({ src, ...props }) if (parsedValues) { @@ -24,5 +29,6 @@ export const StaticImage: React.FC = ({ return } - return

Not an image

+ console.warn(`Image not loaded`, src) + return null } diff --git a/packages/gatsby-plugin-static-image/src/utils.ts b/packages/gatsby-plugin-static-image/src/utils.ts index 14bbd3b66ef67..e9000a6e70ca4 100644 --- a/packages/gatsby-plugin-static-image/src/utils.ts +++ b/packages/gatsby-plugin-static-image/src/utils.ts @@ -1,74 +1,7 @@ import murmurhash from "babel-plugin-remove-graphql-queries/murmur" - -import { - Expression, - SpreadElement, - JSXEmptyExpression, - JSXAttribute, - JSXSpreadAttribute, - JSXIdentifier, - JSXNamespacedName, -} from "@babel/types" - -export function parseIdentifier( - identifier: JSXIdentifier | JSXNamespacedName -): string { - if (identifier.type === `JSXIdentifier`) { - return identifier.name - } - return parseIdentifier(identifier.name) -} - -export function parseJSXAttribute( - attribute: JSXAttribute | JSXSpreadAttribute -): unknown { - if (attribute.type === `JSXSpreadAttribute`) { - return undefined - } - if (!attribute.value) { - return true - } - - switch (attribute.value.type) { - case `StringLiteral`: - return attribute.value.value - - case `JSXExpressionContainer`: - return parseAttributeValue(attribute.value.expression) - - default: - console.warn( - `Invalid type ${attribute.value.type} for attribute ${parseIdentifier( - attribute.name - )}` - ) - return undefined - } -} - -export function parseAttributeValue( - value: Expression | SpreadElement | null | JSXEmptyExpression -): unknown { - if (value === null) { - return true - } - switch (value.type) { - case `StringLiteral`: - case `NumericLiteral`: - case `BooleanLiteral`: - return value.value - - case `TemplateLiteral`: - if (value.expressions.length || value.quasis.length !== 1) { - return null - } - return value.quasis[0].value.raw - - case `ArrayExpression`: - return value.elements.map(parseAttributeValue) - } - return undefined -} +import { JSXOpeningElement } from "@babel/types" +import { NodePath } from "@babel/core" +import { evaluateAttributes } from "./jsx-utils" export const SHARP_ATTRIBUTES = new Set([ `src`, @@ -91,33 +24,16 @@ export const SHARP_ATTRIBUTES = new Set([ `width`, `height`, ]) - -export function parseAttributes( - attributes: Array +export function evaluateImageAttributes( + nodePath: NodePath, + onError?: (prop: string) => void ): Record { - return attributes.reduce((prev, attribute): Record => { - if (attribute.type == `JSXSpreadAttribute`) { - return prev - } - const identifier = parseIdentifier(attribute.name) - if (!SHARP_ATTRIBUTES.has(identifier)) { - return prev - } - prev[identifier] = parseJSXAttribute(attribute) - return prev - }, {} as Record) + return evaluateAttributes(nodePath, onError, SHARP_ATTRIBUTES) } export function hashOptions(options: unknown): string { return `${murmurhash(JSON.stringify(options))}` } - -export function hashAttributes( - attributes: Array -): string { - return hashOptions(parseAttributes(attributes)) -} - export interface ISomeGatsbyImageProps { fadeIn?: boolean durationFadeIn?: number From b430e5afb3be4d236ead4b00e71f43bc7b62e906 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 18 Sep 2020 12:18:52 +0100 Subject: [PATCH 005/143] Use jsx utils package --- .../gatsby-plugin-static-image/package.json | 8 +- .../src/jsx-utils.ts | 74 ------------------- .../gatsby-plugin-static-image/src/utils.ts | 7 +- yarn.lock | 23 +++++- 4 files changed, 32 insertions(+), 80 deletions(-) delete mode 100644 packages/gatsby-plugin-static-image/src/jsx-utils.ts diff --git a/packages/gatsby-plugin-static-image/package.json b/packages/gatsby-plugin-static-image/package.json index 1ec4a100abf00..661fc9d935a8b 100644 --- a/packages/gatsby-plugin-static-image/package.json +++ b/packages/gatsby-plugin-static-image/package.json @@ -15,10 +15,13 @@ "types": "dist/index.d.ts", "devDependencies": { "@types/node": "^14.10.2", + "@babel/cli": "^7.8.7", "@babel/core": "^7.8.7", "@types/babel__core": "^7.1.6", "@types/babel__traverse": "^7.0.9", - "@types/fs-extra": "^8.1.0" + "@types/fs-extra": "^8.1.0", + "cross-env": "^7.0.2", + "typescript": "^3.9.5" }, "peerDependencies": { "gatsby": ">=2", @@ -31,7 +34,8 @@ "@babel/parser": "^7.8.7", "@babel/traverse": "^7.8.6", "babel-plugin-remove-graphql-queries": "^2.9.19", - "fs-extra": "^8.1.0" + "fs-extra": "^8.1.0", + "babel-jsx-utils": "^1.0.1" }, "main": "index.js", "repository": { diff --git a/packages/gatsby-plugin-static-image/src/jsx-utils.ts b/packages/gatsby-plugin-static-image/src/jsx-utils.ts deleted file mode 100644 index 9c9a5baa6958a..0000000000000 --- a/packages/gatsby-plugin-static-image/src/jsx-utils.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { - JSXAttribute, - JSXIdentifier, - JSXNamespacedName, - JSXOpeningElement, -} from "@babel/types" -import { NodePath } from "@babel/core" - -export function parseIdentifier( - identifier: JSXIdentifier | JSXNamespacedName -): string { - if (identifier.type === `JSXIdentifier`) { - return identifier.name - } - return parseIdentifier(identifier.name) -} - -export function evaluateAttributes( - nodePath: NodePath, - onError?: (prop: string) => void, - include?: Set -): Record { - let result = {} - - nodePath.traverse({ - JSXSpreadAttribute(attrPath) { - const spreadValues = attrPath.get(`argument`).evaluate() - if (spreadValues.confident) { - result = { ...result, ...spreadValues.value } - } else { - // eslint-disable-next-line no-unused-expressions - onError?.(``) - } - }, - JSXAttribute(attrPath) { - const prop = parseIdentifier(attrPath.node.name) - if (include && !include.has(prop)) { - return - } - const { value, confident } = evaluateAttributeValue(attrPath) - if (confident) { - result[prop] = value - } else { - // eslint-disable-next-line no-unused-expressions - onError?.(prop) - } - }, - }) - - return result -} - -export function evaluateAttributeValue( - nodePath: NodePath -): { - confident: boolean - value: unknown -} { - const valueNode = nodePath.get(`value`) - if (!valueNode.node) { - // empty attributes are truthy - return { confident: true, value: true } - } else if (valueNode.node.type === `JSXExpressionContainer`) { - const expression = valueNode.get(`expression`) - - if (Array.isArray(expression)) { - return expression[0]?.evaluate() - } - - return expression.evaluate() - } else { - return valueNode.evaluate() - } -} diff --git a/packages/gatsby-plugin-static-image/src/utils.ts b/packages/gatsby-plugin-static-image/src/utils.ts index e9000a6e70ca4..6ac7a34a46e22 100644 --- a/packages/gatsby-plugin-static-image/src/utils.ts +++ b/packages/gatsby-plugin-static-image/src/utils.ts @@ -1,7 +1,7 @@ -import murmurhash from "babel-plugin-remove-graphql-queries/murmur" +import { murmurhash } from "babel-plugin-remove-graphql-queries/murmur" import { JSXOpeningElement } from "@babel/types" import { NodePath } from "@babel/core" -import { evaluateAttributes } from "./jsx-utils" +import { getAttributeValues } from "babel-jsx-utils" export const SHARP_ATTRIBUTES = new Set([ `src`, @@ -24,11 +24,12 @@ export const SHARP_ATTRIBUTES = new Set([ `width`, `height`, ]) + export function evaluateImageAttributes( nodePath: NodePath, onError?: (prop: string) => void ): Record { - return evaluateAttributes(nodePath, onError, SHARP_ATTRIBUTES) + return getAttributeValues(nodePath, onError, SHARP_ATTRIBUTES) } export function hashOptions(options: unknown): string { diff --git a/yarn.lock b/yarn.lock index a5b194c27a088..fda030437d804 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,7 +40,7 @@ asciidoctor-opal-runtime "0.3.0" unxhr "1.0.1" -"@babel/cli@^7.11.6": +"@babel/cli@^7.11.6", "@babel/cli@^7.8.7": version "7.11.6" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.11.6.tgz#1fcbe61c2a6900c3539c06ee58901141f3558482" integrity sha512-+w7BZCvkewSmaRM6H4L2QM3RL90teqEIHDIFXAmrW33+0jhlymnDAEdqVeCZATvxhQuio1ifoGVlJJbIiH9Ffg== @@ -5335,6 +5335,11 @@ babel-jest@^24.9.0: chalk "^2.4.2" slash "^2.0.0" +babel-jsx-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-jsx-utils/-/babel-jsx-utils-1.0.1.tgz#f2d171cba526594e7d69e9893d634502cf950f07" + integrity sha512-Qph/atlQiSvfmkoIZ9VA+t8dA0ex2TwL37rkhLT9YLJdhaTMZ2HSM2QGzbqjLWanKA+I3wRQJjjhtuIEgesuLw== + babel-loader@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" @@ -7717,6 +7722,13 @@ cross-env@^5.2.1: dependencies: cross-spawn "^6.0.5" +cross-env@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9" + integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw== + dependencies: + cross-spawn "^7.0.1" + cross-fetch@2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.2.tgz#a47ff4f7fc712daba8f6a695a11c948440d45723" @@ -7765,6 +7777,15 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" +cross-spawn@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" From 96df1b82dc50c9dcba13cafc450de0adc4c920ec Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 18 Sep 2020 15:46:08 +0100 Subject: [PATCH 006/143] Fix typings --- packages/gatsby-plugin-static-image/package.json | 1 + packages/gatsby-plugin-static-image/src/image-processing.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-plugin-static-image/package.json b/packages/gatsby-plugin-static-image/package.json index 661fc9d935a8b..959a1967e9c99 100644 --- a/packages/gatsby-plugin-static-image/package.json +++ b/packages/gatsby-plugin-static-image/package.json @@ -25,6 +25,7 @@ }, "peerDependencies": { "gatsby": ">=2", + "gatsby-cli": "*", "gatsby-core-utils": "*", "gatsby-image": "*", "gatsby-plugin-sharp": "*", diff --git a/packages/gatsby-plugin-static-image/src/image-processing.ts b/packages/gatsby-plugin-static-image/src/image-processing.ts index 4712966c309be..513d2edea4e51 100644 --- a/packages/gatsby-plugin-static-image/src/image-processing.ts +++ b/packages/gatsby-plugin-static-image/src/image-processing.ts @@ -1,4 +1,6 @@ -import { Node, Reporter, GatsbyCache } from "gatsby" +import { Node, GatsbyCache } from "gatsby" +import type { reporter } from "gatsby-cli/lib/reporter" +type Reporter = typeof reporter import { fluid as fluidSharp, fixed as fixedSharp } from "gatsby-plugin-sharp" const fileCache = new Map() import fs from "fs-extra" From f98f56a577a71b6f0bb3bc27c812670b50515781 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 18 Sep 2020 17:54:50 +0100 Subject: [PATCH 007/143] Typecheck fix --- packages/gatsby-plugin-static-image/tsconfig.json | 3 +++ yarn.lock | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-plugin-static-image/tsconfig.json b/packages/gatsby-plugin-static-image/tsconfig.json index dd788c21d6cab..1772bddb1272d 100644 --- a/packages/gatsby-plugin-static-image/tsconfig.json +++ b/packages/gatsby-plugin-static-image/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../../tsconfig.json", + "compilerOptions": { + "skipLibCheck": true + }, "exclude": ["node_modules", "src/__tests__", "src/__mocks__", "dist"] } diff --git a/yarn.lock b/yarn.lock index fda030437d804..1f0c153075913 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5467,7 +5467,7 @@ babel-plugin-jest-hoist@^24.9.0: dependencies: "@types/babel__traverse" "^7.0.6" -babel-plugin-lodash@^3.3.4: +babel-plugin-lodash@3.3.4, babel-plugin-lodash@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/babel-plugin-lodash/-/babel-plugin-lodash-3.3.4.tgz#4f6844358a1340baed182adbeffa8df9967bc196" integrity sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg== From e48da0a2f6e927cb4912a3d10370d04916027a04 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Mon, 21 Sep 2020 11:51:39 +0100 Subject: [PATCH 008/143] Fix repo fields --- packages/gatsby-plugin-static-image/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-plugin-static-image/package.json b/packages/gatsby-plugin-static-image/package.json index 959a1967e9c99..a420997d8f22b 100644 --- a/packages/gatsby-plugin-static-image/package.json +++ b/packages/gatsby-plugin-static-image/package.json @@ -13,6 +13,7 @@ "gatsby-node.js" ], "types": "dist/index.d.ts", + "homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-static-image#readme", "devDependencies": { "@types/node": "^14.10.2", "@babel/cli": "^7.8.7", @@ -42,7 +43,7 @@ "repository": { "type": "git", "url": "https://github.com/gatsbyjs/gatsby.git", - "directory": "packages/gatsby-plugin-static-images" + "directory": "packages/gatsby-plugin-static-image" }, "author": "Matt Kane ", "license": "MIT" From c11c2a47d8919f56bfa926ba9b2ee0a512b468b8 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Mon, 21 Sep 2020 11:52:29 +0100 Subject: [PATCH 009/143] Helpful warning --- packages/gatsby-plugin-static-image/src/static-image.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/gatsby-plugin-static-image/src/static-image.tsx b/packages/gatsby-plugin-static-image/src/static-image.tsx index 2e2fd58cbda4c..5c109f2c9e163 100644 --- a/packages/gatsby-plugin-static-image/src/static-image.tsx +++ b/packages/gatsby-plugin-static-image/src/static-image.tsx @@ -30,5 +30,10 @@ export const StaticImage: React.FC = ({ return } console.warn(`Image not loaded`, src) + if (!__error && process.env.NODE_ENV === `development`) { + console.warn( + `Please ensure that "gatsby-plugin-static-image" is included in the plugins array in gatsby-config.js` + ) + } return null } From 7f16e800a25d15b9f14e6903b9bbea7402245f10 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 22 Sep 2020 09:56:44 +0100 Subject: [PATCH 010/143] Re-enable duotone --- packages/gatsby-plugin-static-image/src/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/gatsby-plugin-static-image/src/utils.ts b/packages/gatsby-plugin-static-image/src/utils.ts index 6ac7a34a46e22..15fe9319255d6 100644 --- a/packages/gatsby-plugin-static-image/src/utils.ts +++ b/packages/gatsby-plugin-static-image/src/utils.ts @@ -14,6 +14,7 @@ export const SHARP_ATTRIBUTES = new Set([ `cropFocus`, `pngCompressionSpeed`, `rotate`, + `duotone`, `fluid`, `fixed`, `maxWidth`, @@ -65,6 +66,7 @@ export interface ICommonImageProps { pngQuality?: number webpQuality?: number grayscale?: boolean + duotone?: false | { highlight: string; shadow: string } toFormat?: "NO_CHANGE" | "JPG" | "PNG" | "WEBP" cropFocus?: | "CENTER" From 146bd6e7f4835d184343c17a2b2e9ce088f8ff17 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 22 Sep 2020 09:56:56 +0100 Subject: [PATCH 011/143] Update readme --- packages/gatsby-plugin-static-image/README.md | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/gatsby-plugin-static-image/README.md b/packages/gatsby-plugin-static-image/README.md index f78016e2b2720..ecb6a382d4625 100644 --- a/packages/gatsby-plugin-static-image/README.md +++ b/packages/gatsby-plugin-static-image/README.md @@ -80,11 +80,11 @@ export const Dino = () => { ## How does it work? -When your site is compiled, any references to StaticImage components are extracted, the images are resized by Sharp in a similar way to `gatsby-transformer-sharp`, and then the resulting sharp object is written to `.cache/caches/gatsby-plugin-static-image/`, with the filename generated as a hash of the normalized image props. Next, a Babel plugin finds any references to StaticImage, calculates the same hash, loads the JSON file with the sharp object, then adds it as a new `parsedValues` prop. It then returns a GatsbyImage, passing the parsedValues as the fixed or fluid prop. +When your site is compiled, any references to StaticImage components are extracted, the images are resized by Sharp in a similar way to `gatsby-transformer-sharp`, and then the resulting sharp object is written to `.cache/caches/gatsby-plugin-static-image/`, with the filename generated as a hash of the normalized image props. Next, a Babel plugin finds any references to StaticImage, calculates the same hash, loads the JSON file with the sharp object, then adds it as a new `parsedValues` prop. It then returns a GatsbyImage, passing the parsedValues as the fixed or fluid prop. Errors don't cause the build to fail, but instead are written to the component as an `__error` prop, which is then logged in develop. ### Are there restrictions to how this is used? -You cannot pass variables or expressions to the props: they must be literal values, and not spread. For example, this is forbidden: +The props must be able to be statically-analyzed at build time. You can't pass them as props from outside the component, or use the results of function calls, for example. ```js //Doesn't work @@ -96,11 +96,30 @@ You cannot pass variables or expressions to the props: they must be literal valu ```js //Doesn't work () => { - const width = 200; + const width = getTheWidthFromSomewhere(); return } ``` +You can use variables and expressions if they're in the scope of the component, e.g.: + +```js +//OK +() => { + const width = 300 + return +} +``` + +```js +//Also OK +() => { + const width = 300 + const height = width * 16 / 9 + return +} +``` + ## Installation ```bash @@ -113,6 +132,7 @@ npm install gatsby-plugin-static-image module.exports = { //... plugins: [ + "gatsby-plugin-sharp", "gatsby-plugin-static-image", //... ], @@ -162,8 +182,7 @@ export interface CommonImageProps { pngQuality?: number webpQuality?: number grayscale?: boolean - // Disabled for now because parsing objects is annoying. One day. - // duotone?: false | { highlight: string; shadow: string } + duotone?: false | { highlight: string; shadow: string } toFormat?: "NO_CHANGE" | "JPG" | "PNG" | "WEBP" cropFocus?: | "CENTER" From f46f3e08622ea4e6d6b9de39283f64a9996a9ec9 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 22 Sep 2020 09:57:08 +0100 Subject: [PATCH 012/143] Improve typings --- .../gatsby-plugin-static-image/src/image-processing.ts | 7 +++---- packages/gatsby-plugin-static-image/src/types.d.ts | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/gatsby-plugin-static-image/src/image-processing.ts b/packages/gatsby-plugin-static-image/src/image-processing.ts index 513d2edea4e51..703bc7853b6ee 100644 --- a/packages/gatsby-plugin-static-image/src/image-processing.ts +++ b/packages/gatsby-plugin-static-image/src/image-processing.ts @@ -1,11 +1,10 @@ -import { Node, GatsbyCache } from "gatsby" -import type { reporter } from "gatsby-cli/lib/reporter" -type Reporter = typeof reporter +import { Node, GatsbyCache, Reporter } from "gatsby" import { fluid as fluidSharp, fixed as fixedSharp } from "gatsby-plugin-sharp" -const fileCache = new Map() import fs from "fs-extra" import path from "path" +const fileCache = new Map() + export async function writeImages({ images, cacheDir, diff --git a/packages/gatsby-plugin-static-image/src/types.d.ts b/packages/gatsby-plugin-static-image/src/types.d.ts index ac6d5adeaba1c..c17b828e96bdb 100644 --- a/packages/gatsby-plugin-static-image/src/types.d.ts +++ b/packages/gatsby-plugin-static-image/src/types.d.ts @@ -1,4 +1,3 @@ declare module "babel-plugin-remove-graphql-queries/murmur" declare module "gatsby-plugin-sharp/plugin-options" declare module "gatsby-plugin-sharp" -declare module "gatsby-cli/lib/reporter" From 45a223f1b7c3e99eb62bd0192f900de0eedb5f33 Mon Sep 17 00:00:00 2001 From: Kyle Gill Date: Wed, 30 Sep 2020 14:15:13 -0600 Subject: [PATCH 013/143] wip fixed image fields for tracedSVG and webP --- .../src/get-custom-sharp-fields.ts | 55 +++++++++++++++++++ .../src/image-processing.ts | 19 ++++++- .../gatsby-plugin-static-image/src/utils.ts | 2 + 3 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts diff --git a/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts b/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts new file mode 100644 index 0000000000000..2d4c8c7dab1f5 --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts @@ -0,0 +1,55 @@ +import { Node, GatsbyCache, Reporter } from "gatsby" +import { fluid, fixed, traceSVG } from "gatsby-plugin-sharp" + +export async function getCustomSharpFields({ + isFixed, // make sure fluid is also handled + file, + args, + reporter, + cache, +}: { + isFixed: boolean + file: Node + args: any // TODO type this correctly + reporter: Reporter + cache: GatsbyCache +}): Promise<{ + srcWebP: string | null + srcSetWebP: string | null + tracedSVG: string | null +}> { + const { webP, tracedSVG } = args + const customSharpFields = { + srcWebP: null, + srcSetWebP: null, + tracedSVG: null, + } + + if (webP) { + // If the file is already in webp format or should explicitly + // be converted to webp, we do not create additional webp files + if (file.extension !== `webp`) { + const fixedWebP = await fixed({ + file, + args: { ...args, toFormat: `webp` }, + reporter, + cache, + }) + customSharpFields.srcWebP = fixedWebP.src + customSharpFields.srcSetWebP = fixedWebP.srcSet + } + } + + if (tracedSVG) { + const tracedSVG = await traceSVG({ + file, + args: { ...args, traceSVG: true }, + fileArgs: args, + cache, + reporter, + }) + customSharpFields.tracedSVG = tracedSVG + } + + return customSharpFields +} diff --git a/packages/gatsby-plugin-static-image/src/image-processing.ts b/packages/gatsby-plugin-static-image/src/image-processing.ts index 703bc7853b6ee..ed0889984852e 100644 --- a/packages/gatsby-plugin-static-image/src/image-processing.ts +++ b/packages/gatsby-plugin-static-image/src/image-processing.ts @@ -2,6 +2,7 @@ import { Node, GatsbyCache, Reporter } from "gatsby" import { fluid as fluidSharp, fixed as fixedSharp } from "gatsby-plugin-sharp" import fs from "fs-extra" import path from "path" +import { getCustomSharpFields } from "./get-custom-sharp-fields" const fileCache = new Map() @@ -19,7 +20,7 @@ export async function writeImages({ cache: GatsbyCache }): Promise { const promises = [...images.entries()].map( - async ([hash, { src, fluid, fixed, ...args }]) => { + async ([hash, { src, fluid, fixed, webP, tracedSVG, ...args }]) => { const isFixed = fixed ?? !fluid let file = fileCache.get(src as string) @@ -34,9 +35,21 @@ export async function writeImages({ const filename = path.join(cacheDir, `${hash}.json`) try { const options = { file, args, reporter, cache } - const data = await (isFixed ? fixedSharp(options) : fluidSharp(options)) + // get standard set of fields from sharp + const sharpData = await (isFixed + ? fixedSharp(options) + : fluidSharp(options)) + if (sharpData) { + // get the equivalent webP and tracedSVG fields gatsby-transformer-sharp handles normally + const customSharpFields = await getCustomSharpFields({ + isFixed: isFixed ? true : false, + file, + args, + reporter, + cache, + }) + const data = { ...sharpData, ...customSharpFields } - if (data) { await fs.writeJSON(filename, data) } else { console.log(`Could not process image`) diff --git a/packages/gatsby-plugin-static-image/src/utils.ts b/packages/gatsby-plugin-static-image/src/utils.ts index 15fe9319255d6..e1e492e18e3f9 100644 --- a/packages/gatsby-plugin-static-image/src/utils.ts +++ b/packages/gatsby-plugin-static-image/src/utils.ts @@ -24,6 +24,8 @@ export const SHARP_ATTRIBUTES = new Set([ `background`, `width`, `height`, + `tracedSVG`, + `webP`, ]) export function evaluateImageAttributes( From 836fe1d02ce371c08e274cc7e6d639bae6068072 Mon Sep 17 00:00:00 2001 From: Kyle Gill Date: Wed, 30 Sep 2020 17:00:10 -0600 Subject: [PATCH 014/143] handle fluid images as well --- .../src/get-custom-sharp-fields.ts | 28 ++++++++++++------- .../src/image-processing.ts | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts b/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts index 2d4c8c7dab1f5..c1b2260c56c1a 100644 --- a/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts +++ b/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts @@ -2,7 +2,7 @@ import { Node, GatsbyCache, Reporter } from "gatsby" import { fluid, fixed, traceSVG } from "gatsby-plugin-sharp" export async function getCustomSharpFields({ - isFixed, // make sure fluid is also handled + isFixed, file, args, reporter, @@ -10,7 +10,7 @@ export async function getCustomSharpFields({ }: { isFixed: boolean file: Node - args: any // TODO type this correctly + args: any // TODO: type this correctly reporter: Reporter cache: GatsbyCache }): Promise<{ @@ -29,14 +29,22 @@ export async function getCustomSharpFields({ // If the file is already in webp format or should explicitly // be converted to webp, we do not create additional webp files if (file.extension !== `webp`) { - const fixedWebP = await fixed({ - file, - args: { ...args, toFormat: `webp` }, - reporter, - cache, - }) - customSharpFields.srcWebP = fixedWebP.src - customSharpFields.srcSetWebP = fixedWebP.srcSet + const generatedWebP = await (isFixed + ? fixed({ + file, + // TODO: need to get access to pathPrefix into these invocations + args: { ...args, toFormat: `webp` }, + reporter, + cache, + }) + : fluid({ + file, + args: { ...args, toFormat: `webp` }, + reporter, + cache, + })) + customSharpFields.srcWebP = generatedWebP.src + customSharpFields.srcSetWebP = generatedWebP.srcSet } } diff --git a/packages/gatsby-plugin-static-image/src/image-processing.ts b/packages/gatsby-plugin-static-image/src/image-processing.ts index ed0889984852e..7c902c01b2819 100644 --- a/packages/gatsby-plugin-static-image/src/image-processing.ts +++ b/packages/gatsby-plugin-static-image/src/image-processing.ts @@ -20,7 +20,7 @@ export async function writeImages({ cache: GatsbyCache }): Promise { const promises = [...images.entries()].map( - async ([hash, { src, fluid, fixed, webP, tracedSVG, ...args }]) => { + async ([hash, { src, fluid, fixed, ...args }]) => { const isFixed = fixed ?? !fluid let file = fileCache.get(src as string) From 97a5b77aa11bb625701f446c4b1bdb867d16d774 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 1 Oct 2020 09:28:05 +0100 Subject: [PATCH 015/143] Use require rather than JSON import --- .../src/babel-plugin-parse-static-images.ts | 23 +++++++++---------- .../src/preprocess-source.ts | 3 --- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts b/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts index 42f8c4fbba8b4..17b33ba5b3d58 100644 --- a/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts +++ b/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts @@ -9,9 +9,8 @@ import { BooleanLiteral, ArrayExpression, TemplateLiteral, - ObjectProperty, } from "@babel/types" - +import template from "@babel/template" type AttrType = | StringLiteral | NumericLiteral @@ -57,10 +56,6 @@ export default function attrs({ ) } - function generateProperties([key, val]): ObjectProperty { - return t.objectProperty(t.stringLiteral(key), generateLiteral(val)) - } - return { visitor: { JSXOpeningElement(nodePath): void { @@ -85,6 +80,8 @@ export default function attrs({ console.warn(error) } + const noSrc = errors.includes(`src`) + const hash = hashOptions(props) const cacheDir = (this.opts as Record)?.cacheDir @@ -95,10 +92,12 @@ export default function attrs({ const filename = path.join(cacheDir, `${hash}.json`) let data: Record | undefined - try { - data = fs.readJSONSync(filename) - } catch (e) { - console.warn(`Could not read file ${filename}`, e) + if (!noSrc) { + try { + data = fs.readJSONSync(filename) + } catch (e) { + console.warn(`Could not read file ${filename}`, e) + } } if (!data) { @@ -122,12 +121,12 @@ export default function attrs({ nodePath.node.attributes.push(newProp) } - const expressions = Object.entries(data).map(generateProperties) + const makeRequire = template.expression(`require("${filename}")`) const newProp = t.jsxAttribute( t.jsxIdentifier(`parsedValues`), - t.jsxExpressionContainer(t.objectExpression(expressions)) + t.jsxExpressionContainer(makeRequire()) ) nodePath.node.attributes.push(newProp) diff --git a/packages/gatsby-plugin-static-image/src/preprocess-source.ts b/packages/gatsby-plugin-static-image/src/preprocess-source.ts index d2595d4416b21..0e1562fd7ae6a 100644 --- a/packages/gatsby-plugin-static-image/src/preprocess-source.ts +++ b/packages/gatsby-plugin-static-image/src/preprocess-source.ts @@ -19,7 +19,6 @@ export async function preprocessSource({ !contents.includes(`gatsby-plugin-static-image`) || !extensions.includes(path.extname(filename)) ) { - console.log(`not found in`, filename) return contents } const root = store.getState().program.directory @@ -31,8 +30,6 @@ export async function preprocessSource({ `gatsby-plugin-static-image` ) - console.log(`yes found in`, filename) - const ast = babelParseToAst(contents, filename) const images = extractStaticImageProps(ast) From 46e5807729d0af5b5cfb8ad5f9dad465301ca606 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 1 Oct 2020 17:10:39 +0100 Subject: [PATCH 016/143] Watch files for changes, and use relative paths --- .../src/gatsby-node.ts | 2 + .../src/image-processing.ts | 86 +++++++++++++------ .../src/on-create-node.ts | 33 +++++++ .../gatsby-plugin-static-image/src/parser.ts | 6 +- .../src/preprocess-source.ts | 15 ++-- .../gatsby-plugin-static-image/src/utils.ts | 2 +- 6 files changed, 111 insertions(+), 33 deletions(-) create mode 100644 packages/gatsby-plugin-static-image/src/on-create-node.ts diff --git a/packages/gatsby-plugin-static-image/src/gatsby-node.ts b/packages/gatsby-plugin-static-image/src/gatsby-node.ts index 1b1bd964b0f21..730291bd51e36 100644 --- a/packages/gatsby-plugin-static-image/src/gatsby-node.ts +++ b/packages/gatsby-plugin-static-image/src/gatsby-node.ts @@ -1,4 +1,6 @@ export * from "./preprocess-source" +export * from "./on-create-node" + import { GatsbyNode } from "gatsby" import path from "path" diff --git a/packages/gatsby-plugin-static-image/src/image-processing.ts b/packages/gatsby-plugin-static-image/src/image-processing.ts index 703bc7853b6ee..90b9ee268e12a 100644 --- a/packages/gatsby-plugin-static-image/src/image-processing.ts +++ b/packages/gatsby-plugin-static-image/src/image-processing.ts @@ -1,52 +1,90 @@ -import { Node, GatsbyCache, Reporter } from "gatsby" +import { Node, GatsbyCache, Reporter, ParentSpanPluginArgs } from "gatsby" import { fluid as fluidSharp, fixed as fixedSharp } from "gatsby-plugin-sharp" +import { createFileNode } from "gatsby-source-filesystem/create-file-node" import fs from "fs-extra" import path from "path" +import { ImageProps } from "./utils" -const fileCache = new Map() +export interface IImageMetadata { + isFixed: boolean + contentDigest?: string + args: Record + cacheFilename: string +} export async function writeImages({ images, cacheDir, - files, reporter, cache, + sourceDir, + createNodeId, }: { - images: Map> + images: Map cacheDir: string - files: Array reporter: Reporter cache: GatsbyCache + sourceDir: string + createNodeId: ParentSpanPluginArgs["createNodeId"] }): Promise { const promises = [...images.entries()].map( async ([hash, { src, fluid, fixed, ...args }]) => { - const isFixed = fixed ?? !fluid + // Default to fixed, but allow specifying either + const isFixed = !!(fixed ?? !fluid) + const fullPath = path.resolve(sourceDir, src) - let file = fileCache.get(src as string) - if (!file) { - // Is there a more efficient way to search for a fileNode by relativePath? - file = files.find(node => node.relativePath === src) + if (!fs.existsSync(fullPath)) { + reporter.warn(`Could not find image "${src}". Looked for ${fullPath}`) + return } + const file = await createFileNode(fullPath, createNodeId, {}) + if (!file) { - reporter.warn(`Image not found ${src}`) + reporter.warn(`Could not create node for image ${src}`) return } - const filename = path.join(cacheDir, `${hash}.json`) - try { - const options = { file, args, reporter, cache } - const data = await (isFixed ? fixedSharp(options) : fluidSharp(options)) - - if (data) { - await fs.writeJSON(filename, data) - } else { - console.log(`Could not process image`) - } - } catch (e) { - // TODO: Report errors properly - console.log(`Error processing image`, e) + + // This is a cache of file node to static image mappings + const cacheKey = `ref-${file.id}` + + const imageRefs: Map = + (await cache.get(cacheKey)) || {} + + const cacheFilename = path.join(cacheDir, `${hash}.json`) + imageRefs[hash] = { + isFixed, + contentDigest: file.internal?.contentDigest, + args, + cacheFilename, } + await cache.set(cacheKey, imageRefs) + + await writeImage(file, args, reporter, cache, isFixed, cacheFilename) } ) return Promise.all(promises).then(() => {}) } + +export async function writeImage( + file: Node, + args: Omit, + reporter: Reporter, + cache: GatsbyCache, + isFixed: boolean, + filename: string +): Promise { + try { + const options = { file, args, reporter, cache } + const data = await (isFixed ? fixedSharp(options) : fluidSharp(options)) + + if (data) { + await fs.writeJSON(filename, data) + } else { + console.log(`Could not process image`) + } + } catch (e) { + // TODO: Report errors properly + console.log(`Error processing image`, e) + } +} diff --git a/packages/gatsby-plugin-static-image/src/on-create-node.ts b/packages/gatsby-plugin-static-image/src/on-create-node.ts new file mode 100644 index 0000000000000..87507c9b80502 --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/on-create-node.ts @@ -0,0 +1,33 @@ +import { CreateNodeArgs } from "gatsby" +import { IImageMetadata, writeImage } from "./image-processing" +export async function onCreateNode({ + node, + cache, + reporter, +}: CreateNodeArgs): Promise { + if (node.internal.type !== `File`) { + return + } + + // See if any static image instances use this source file + const imageRefs: Record = await cache.get( + `ref-${node.id}` + ) + + if (!imageRefs) { + return + } + + await Promise.all( + Object.values(imageRefs).map( + async ({ isFixed, contentDigest, args, cacheFilename }) => { + if (contentDigest && contentDigest === node.internal.contentDigest) { + // Skipping, because the file is unchanged + return + } + // Update the image + await writeImage(node, args, reporter, cache, isFixed, cacheFilename) + } + ) + ) +} diff --git a/packages/gatsby-plugin-static-image/src/parser.ts b/packages/gatsby-plugin-static-image/src/parser.ts index 45de5f1a890ac..a37112ca44edc 100644 --- a/packages/gatsby-plugin-static-image/src/parser.ts +++ b/packages/gatsby-plugin-static-image/src/parser.ts @@ -1,12 +1,12 @@ import traverse from "@babel/traverse" -import { hashOptions, evaluateImageAttributes } from "./utils" +import { hashOptions, evaluateImageAttributes, ImageProps } from "./utils" import { NodePath } from "@babel/core" import { JSXOpeningElement } from "@babel/types" export const extractStaticImageProps = ( ast: babel.types.File -): Map> => { - const images: Map> = new Map() +): Map => { + const images: Map = new Map() traverse(ast, { JSXOpeningElement(nodePath) { diff --git a/packages/gatsby-plugin-static-image/src/preprocess-source.ts b/packages/gatsby-plugin-static-image/src/preprocess-source.ts index 0e1562fd7ae6a..17aac8579582d 100644 --- a/packages/gatsby-plugin-static-image/src/preprocess-source.ts +++ b/packages/gatsby-plugin-static-image/src/preprocess-source.ts @@ -8,12 +8,11 @@ const extensions: Array = [`.js`, `.jsx`, `.tsx`] export async function preprocessSource({ filename, contents, - getNodesByType, cache, reporter, store, + createNodeId, }: PreprocessSourceArgs): Promise { - console.log(`preprocess`) if ( !contents.includes(`StaticImage`) || !contents.includes(`gatsby-plugin-static-image`) || @@ -34,9 +33,15 @@ export async function preprocessSource({ const images = extractStaticImageProps(ast) - const files = getNodesByType(`File`) - - await writeImages({ images, cache, reporter, files, cacheDir }) + const sourceDir = path.dirname(filename) + await writeImages({ + images, + cache, + reporter, + cacheDir, + sourceDir, + createNodeId, + }) return contents } diff --git a/packages/gatsby-plugin-static-image/src/utils.ts b/packages/gatsby-plugin-static-image/src/utils.ts index 15fe9319255d6..d22e634e6da72 100644 --- a/packages/gatsby-plugin-static-image/src/utils.ts +++ b/packages/gatsby-plugin-static-image/src/utils.ts @@ -97,7 +97,7 @@ export interface IFixedImageProps extends ICommonImageProps { height?: number } -export type ImageProps = IFluidImageProps | IFixedImageProps +export type ImageProps = IFluidImageProps & IFixedImageProps & { src: string } export type AnyImageProps = (IFluidImageProps | IFixedImageProps) & ICommonImageProps From 5e41c306dfeae511758d18a21c3fe19b553a259d Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 1 Oct 2020 17:38:59 +0100 Subject: [PATCH 017/143] Improve types --- .../src/get-custom-sharp-fields.ts | 63 +++++++++---------- .../src/image-processing.ts | 4 +- .../gatsby-plugin-static-image/src/parser.ts | 2 +- .../gatsby-plugin-static-image/src/utils.ts | 13 ++-- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts b/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts index c1b2260c56c1a..bf8ae5c51a159 100644 --- a/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts +++ b/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts @@ -1,5 +1,6 @@ import { Node, GatsbyCache, Reporter } from "gatsby" import { fluid, fixed, traceSVG } from "gatsby-plugin-sharp" +import { SharpProps } from "./utils" export async function getCustomSharpFields({ isFixed, @@ -10,54 +11,50 @@ export async function getCustomSharpFields({ }: { isFixed: boolean file: Node - args: any // TODO: type this correctly + args: SharpProps reporter: Reporter cache: GatsbyCache }): Promise<{ - srcWebP: string | null - srcSetWebP: string | null - tracedSVG: string | null + srcWebP?: string + srcSetWebP?: string + tracedSVG?: string }> { - const { webP, tracedSVG } = args - const customSharpFields = { - srcWebP: null, - srcSetWebP: null, - tracedSVG: null, - } + const { webP, tracedSVG: createTracedSVG } = args + + let srcWebP: string | undefined + let tracedSVG: string | undefined + let srcSetWebP: string | undefined - if (webP) { + if (webP && file.extension !== `webp`) { // If the file is already in webp format or should explicitly // be converted to webp, we do not create additional webp files - if (file.extension !== `webp`) { - const generatedWebP = await (isFixed - ? fixed({ - file, - // TODO: need to get access to pathPrefix into these invocations - args: { ...args, toFormat: `webp` }, - reporter, - cache, - }) - : fluid({ - file, - args: { ...args, toFormat: `webp` }, - reporter, - cache, - })) - customSharpFields.srcWebP = generatedWebP.src - customSharpFields.srcSetWebP = generatedWebP.srcSet - } + const { src, srcSet } = await (isFixed + ? fixed({ + file, + // TODO: need to get access to pathPrefix into these invocations + args: { ...args, toFormat: `webp` }, + reporter, + cache, + }) + : fluid({ + file, + args: { ...args, toFormat: `webp` }, + reporter, + cache, + })) + srcWebP = src + srcSetWebP = srcSet } - if (tracedSVG) { - const tracedSVG = await traceSVG({ + if (createTracedSVG) { + tracedSVG = await traceSVG({ file, args: { ...args, traceSVG: true }, fileArgs: args, cache, reporter, }) - customSharpFields.tracedSVG = tracedSVG } - return customSharpFields + return { srcSetWebP, srcWebP, tracedSVG } } diff --git a/packages/gatsby-plugin-static-image/src/image-processing.ts b/packages/gatsby-plugin-static-image/src/image-processing.ts index ccca6bdfabc83..5ed8677f75c96 100644 --- a/packages/gatsby-plugin-static-image/src/image-processing.ts +++ b/packages/gatsby-plugin-static-image/src/image-processing.ts @@ -3,7 +3,7 @@ import { fluid as fluidSharp, fixed as fixedSharp } from "gatsby-plugin-sharp" import { createFileNode } from "gatsby-source-filesystem/create-file-node" import fs from "fs-extra" import path from "path" -import { ImageProps } from "./utils" +import { ImageProps, SharpProps } from "./utils" import { getCustomSharpFields } from "./get-custom-sharp-fields" export interface IImageMetadata { @@ -69,7 +69,7 @@ export async function writeImages({ export async function writeImage( file: Node, - args: Omit, + args: SharpProps, reporter: Reporter, cache: GatsbyCache, isFixed: boolean, diff --git a/packages/gatsby-plugin-static-image/src/parser.ts b/packages/gatsby-plugin-static-image/src/parser.ts index a37112ca44edc..fdeb87d2513e7 100644 --- a/packages/gatsby-plugin-static-image/src/parser.ts +++ b/packages/gatsby-plugin-static-image/src/parser.ts @@ -20,7 +20,7 @@ export const extractStaticImageProps = ( const image = evaluateImageAttributes( // There's a conflict between the definition of NodePath in @babel/core and @babel/traverse (nodePath as unknown) as NodePath - ) + ) as ImageProps images.set(hashOptions(image), image) }, }) diff --git a/packages/gatsby-plugin-static-image/src/utils.ts b/packages/gatsby-plugin-static-image/src/utils.ts index d07e289cf42ce..2a8305a898f7d 100644 --- a/packages/gatsby-plugin-static-image/src/utils.ts +++ b/packages/gatsby-plugin-static-image/src/utils.ts @@ -38,6 +38,10 @@ export function evaluateImageAttributes( export function hashOptions(options: unknown): string { return `${murmurhash(JSON.stringify(options))}` } + +/** + * Props that are passed to gatsby-image, rather than being used by Sharp + */ export interface ISomeGatsbyImageProps { fadeIn?: boolean durationFadeIn?: number @@ -99,15 +103,16 @@ export interface IFixedImageProps extends ICommonImageProps { height?: number } -export type ImageProps = IFluidImageProps & IFixedImageProps & { src: string } -export type AnyImageProps = (IFluidImageProps | IFixedImageProps) & - ICommonImageProps - export type AllProps = IImageOptions & IFluidImageProps & IFixedImageProps & ISomeGatsbyImageProps & { src: string } +export type ImageProps = Omit +export type SharpProps = Omit +export type AnyImageProps = (IFluidImageProps | IFixedImageProps) & + ICommonImageProps + export interface IImageOptions { webP?: boolean base64?: boolean From 0ebbf06b9c1a7429ab65605968f401e1d0a47352 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 2 Oct 2020 09:04:42 +0100 Subject: [PATCH 018/143] Add type --- packages/gatsby-plugin-static-image/src/types.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/gatsby-plugin-static-image/src/types.d.ts b/packages/gatsby-plugin-static-image/src/types.d.ts index c17b828e96bdb..1430021af7438 100644 --- a/packages/gatsby-plugin-static-image/src/types.d.ts +++ b/packages/gatsby-plugin-static-image/src/types.d.ts @@ -1,3 +1,4 @@ declare module "babel-plugin-remove-graphql-queries/murmur" declare module "gatsby-plugin-sharp/plugin-options" declare module "gatsby-plugin-sharp" +declare module "gatsby-source-filesystem/create-file-node" From 12597fa66f28097f9f2ecaab9cdcba7925a738e2 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 2 Oct 2020 09:29:41 +0100 Subject: [PATCH 019/143] Update yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 3c6cfcd61fcc5..c0808c91f9f3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3562,7 +3562,7 @@ resolved "https://registry.yarnpkg.com/@types/fast-levenshtein/-/fast-levenshtein-0.0.1.tgz#3a3615cf173645c8fca58d051e4e32824e4bd286" integrity sha1-OjYVzxc2Rcj8pY0FHk4ygk5L0oY= -"@types/fs-extra@^8.1.1": +"@types/fs-extra@^8.1.0", "@types/fs-extra@^8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068" integrity sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w== From 4036bb30b33b6e588bad3e257622eb638b860f63 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 2 Oct 2020 11:35:04 +0100 Subject: [PATCH 020/143] Add lots of comments and remove unused stuff --- .../src/babel-plugin-parse-static-images.ts | 71 +++++-------------- .../src/get-custom-sharp-fields.ts | 5 ++ .../gatsby-plugin-static-image/src/parser.ts | 5 ++ .../gatsby-plugin-static-image/src/utils.ts | 1 + 4 files changed, 30 insertions(+), 52 deletions(-) diff --git a/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts b/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts index 17b33ba5b3d58..6f4e0994585df 100644 --- a/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts +++ b/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts @@ -3,59 +3,19 @@ import { PluginObj } from "@babel/core" import { hashOptions, evaluateImageAttributes } from "./utils" import fs from "fs-extra" import path from "path" -import { - StringLiteral, - NumericLiteral, - BooleanLiteral, - ArrayExpression, - TemplateLiteral, -} from "@babel/types" + import template from "@babel/template" -type AttrType = - | StringLiteral - | NumericLiteral - | BooleanLiteral - | ArrayExpression - | TemplateLiteral + +/** + * This is a plugin that finds StaticImage components and injects the image props into the component. + * These props contain the image URLs etc, and were created earlier in the build process + */ export default function attrs({ types: t, }: { types: typeof types }): PluginObj { - function generateLiterals(val: Array): Array { - return val.map(generateLiteral).filter(Boolean) as Array - } - - function generateLiteral( - val - ): - | StringLiteral - | NumericLiteral - | BooleanLiteral - | ArrayExpression - | TemplateLiteral { - switch (typeof val) { - case `string`: - return t.stringLiteral(val) - - case `number`: - return t.numericLiteral(val) - - case `boolean`: - return t.booleanLiteral(val) - - case `object`: - if (Array.isArray(val)) { - return t.arrayExpression(generateLiterals(val)) - } - } - return t.templateLiteral( - [t.templateElement({ raw: JSON.stringify(val) })], - [] - ) - } - return { visitor: { JSXOpeningElement(nodePath): void { @@ -67,20 +27,20 @@ export default function attrs({ return } - const errors: Array = [] + const unresolvedProps: Array = [] const props = evaluateImageAttributes(nodePath, prop => { - errors.push(prop) + unresolvedProps.push(prop) }) let error - if (errors.length) { - error = `Could not find values for the following props at build time: ${errors.join()}` + if (unresolvedProps.length) { + error = `Could not find values for the following props at build time: ${unresolvedProps.join()}` console.warn(error) } - const noSrc = errors.includes(`src`) + const noSrc = unresolvedProps.includes(`src`) const hash = hashOptions(props) @@ -92,6 +52,8 @@ export default function attrs({ const filename = path.join(cacheDir, `${hash}.json`) let data: Record | undefined + + // If there's no src prop there's no point in checking if it exists if (!noSrc) { try { data = fs.readJSONSync(filename) @@ -101,7 +63,9 @@ export default function attrs({ } if (!data) { - console.warn(`No image data found for file ${props.src}`, error) + console.warn(`No image data found for file ${props.src}`) + + // Add the error message to the component so we can show it in the browser const newProp = t.jsxAttribute( t.jsxIdentifier(`__error`), @@ -114,6 +78,8 @@ export default function attrs({ return } if (error) { + // Add the error message to the component so we can show it in the browser + const newProp = t.jsxAttribute( t.jsxIdentifier(`__error`), t.stringLiteral(error) @@ -121,6 +87,7 @@ export default function attrs({ nodePath.node.attributes.push(newProp) } + // `require()` the image data into a component prop const makeRequire = template.expression(`require("${filename}")`) const newProp = t.jsxAttribute( diff --git a/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts b/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts index bf8ae5c51a159..d851cecb0fdfa 100644 --- a/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts +++ b/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts @@ -2,6 +2,11 @@ import { Node, GatsbyCache, Reporter } from "gatsby" import { fluid, fixed, traceSVG } from "gatsby-plugin-sharp" import { SharpProps } from "./utils" +/** + * By default the gatsby-plugin-sharp functions don't create webP or SVG + * images. This function adds them if needed. + */ + export async function getCustomSharpFields({ isFixed, file, diff --git a/packages/gatsby-plugin-static-image/src/parser.ts b/packages/gatsby-plugin-static-image/src/parser.ts index fdeb87d2513e7..e0627820ab95b 100644 --- a/packages/gatsby-plugin-static-image/src/parser.ts +++ b/packages/gatsby-plugin-static-image/src/parser.ts @@ -3,6 +3,10 @@ import { hashOptions, evaluateImageAttributes, ImageProps } from "./utils" import { NodePath } from "@babel/core" import { JSXOpeningElement } from "@babel/types" +/** + * Traverses the parsed source, looking for StaticImage components. + * Extracts and returns the props from any that are found + */ export const extractStaticImageProps = ( ast: babel.types.File ): Map => { @@ -10,6 +14,7 @@ export const extractStaticImageProps = ( traverse(ast, { JSXOpeningElement(nodePath) { + // Is this a StaticImage? if ( !nodePath .get(`name`) diff --git a/packages/gatsby-plugin-static-image/src/utils.ts b/packages/gatsby-plugin-static-image/src/utils.ts index 2a8305a898f7d..486327f5b6358 100644 --- a/packages/gatsby-plugin-static-image/src/utils.ts +++ b/packages/gatsby-plugin-static-image/src/utils.ts @@ -32,6 +32,7 @@ export function evaluateImageAttributes( nodePath: NodePath, onError?: (prop: string) => void ): Record { + // Only get attributes that we need for generating the images return getAttributeValues(nodePath, onError, SHARP_ATTRIBUTES) } From b548429d2d30ca35de10b62d1feed653ffb2d78d Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 2 Oct 2020 11:35:49 +0100 Subject: [PATCH 021/143] Create and watch our own nodes --- .../src/gatsby-node.ts | 1 - .../src/image-processing.ts | 56 ++++++++++-- .../src/on-create-node.ts | 33 ------- .../src/preprocess-source.ts | 2 + .../gatsby-plugin-static-image/src/watcher.ts | 86 +++++++++++++++++++ 5 files changed, 137 insertions(+), 41 deletions(-) delete mode 100644 packages/gatsby-plugin-static-image/src/on-create-node.ts create mode 100644 packages/gatsby-plugin-static-image/src/watcher.ts diff --git a/packages/gatsby-plugin-static-image/src/gatsby-node.ts b/packages/gatsby-plugin-static-image/src/gatsby-node.ts index 730291bd51e36..0d59997c1eb03 100644 --- a/packages/gatsby-plugin-static-image/src/gatsby-node.ts +++ b/packages/gatsby-plugin-static-image/src/gatsby-node.ts @@ -1,5 +1,4 @@ export * from "./preprocess-source" -export * from "./on-create-node" import { GatsbyNode } from "gatsby" import path from "path" diff --git a/packages/gatsby-plugin-static-image/src/image-processing.ts b/packages/gatsby-plugin-static-image/src/image-processing.ts index 5ed8677f75c96..73058c00176be 100644 --- a/packages/gatsby-plugin-static-image/src/image-processing.ts +++ b/packages/gatsby-plugin-static-image/src/image-processing.ts @@ -1,10 +1,17 @@ -import { Node, GatsbyCache, Reporter, ParentSpanPluginArgs } from "gatsby" +import { + Node, + GatsbyCache, + Reporter, + ParentSpanPluginArgs, + Actions, +} from "gatsby" import { fluid as fluidSharp, fixed as fixedSharp } from "gatsby-plugin-sharp" import { createFileNode } from "gatsby-source-filesystem/create-file-node" import fs from "fs-extra" import path from "path" import { ImageProps, SharpProps } from "./utils" import { getCustomSharpFields } from "./get-custom-sharp-fields" +import { watchImage } from "./watcher" export interface IImageMetadata { isFixed: boolean @@ -13,6 +20,31 @@ export interface IImageMetadata { cacheFilename: string } +export async function createImageNode({ + fullPath, + createNodeId, + createNode, +}: { + fullPath: string + createNodeId: ParentSpanPluginArgs["createNodeId"] + createNode: Actions["createNode"] +}): Promise { + if (!fs.existsSync(fullPath)) { + return undefined + } + const file: Node = await createFileNode(fullPath, createNodeId, {}) + + if (!file) { + return undefined + } + + file.internal.type = `StaticImage` + + createNode(file) + + return file +} + export async function writeImages({ images, cacheDir, @@ -20,6 +52,7 @@ export async function writeImages({ cache, sourceDir, createNodeId, + createNode, }: { images: Map cacheDir: string @@ -27,6 +60,7 @@ export async function writeImages({ cache: GatsbyCache sourceDir: string createNodeId: ParentSpanPluginArgs["createNodeId"] + createNode: Actions["createNode"] }): Promise { const promises = [...images.entries()].map( async ([hash, { src, fluid, fixed, ...args }]) => { @@ -38,19 +72,25 @@ export async function writeImages({ reporter.warn(`Could not find image "${src}". Looked for ${fullPath}`) return } - const file = await createFileNode(fullPath, createNodeId, {}) + const file: Node = await createFileNode(fullPath, createNodeId, {}) if (!file) { reporter.warn(`Could not create node for image ${src}`) return } - // This is a cache of file node to static image mappings + // We need our own type, because `File` belongs to the filesystem plugin + file.internal.type = `StaticImage` + + createNode(file) + const cacheKey = `ref-${file.id}` + // This is a cache of file node to static image mappings const imageRefs: Map = (await cache.get(cacheKey)) || {} + // Different cache: this is the one with the image properties const cacheFilename = path.join(cacheDir, `${hash}.json`) imageRefs[hash] = { isFixed, @@ -61,6 +101,9 @@ export async function writeImages({ await cache.set(cacheKey, imageRefs) await writeImage(file, args, reporter, cache, isFixed, cacheFilename) + + // Watch the source image for changes + watchImage({ createNode, createNodeId, fullPath, cache, reporter }) } ) @@ -91,13 +134,12 @@ export async function writeImage( cache, }) const data = { ...sharpData, ...customSharpFields } - + // Write the image properties to the cache await fs.writeJSON(filename, data) } else { - console.log(`Could not process image`) + reporter.warn(`Could not process image`) } } catch (e) { - // TODO: Report errors properly - console.log(`Error processing image`, e) + reporter.warn(`Error processing image`) } } diff --git a/packages/gatsby-plugin-static-image/src/on-create-node.ts b/packages/gatsby-plugin-static-image/src/on-create-node.ts deleted file mode 100644 index 87507c9b80502..0000000000000 --- a/packages/gatsby-plugin-static-image/src/on-create-node.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CreateNodeArgs } from "gatsby" -import { IImageMetadata, writeImage } from "./image-processing" -export async function onCreateNode({ - node, - cache, - reporter, -}: CreateNodeArgs): Promise { - if (node.internal.type !== `File`) { - return - } - - // See if any static image instances use this source file - const imageRefs: Record = await cache.get( - `ref-${node.id}` - ) - - if (!imageRefs) { - return - } - - await Promise.all( - Object.values(imageRefs).map( - async ({ isFixed, contentDigest, args, cacheFilename }) => { - if (contentDigest && contentDigest === node.internal.contentDigest) { - // Skipping, because the file is unchanged - return - } - // Update the image - await writeImage(node, args, reporter, cache, isFixed, cacheFilename) - } - ) - ) -} diff --git a/packages/gatsby-plugin-static-image/src/preprocess-source.ts b/packages/gatsby-plugin-static-image/src/preprocess-source.ts index 17aac8579582d..0d83ea4f81a41 100644 --- a/packages/gatsby-plugin-static-image/src/preprocess-source.ts +++ b/packages/gatsby-plugin-static-image/src/preprocess-source.ts @@ -12,6 +12,7 @@ export async function preprocessSource({ reporter, store, createNodeId, + actions: { createNode }, }: PreprocessSourceArgs): Promise { if ( !contents.includes(`StaticImage`) || @@ -41,6 +42,7 @@ export async function preprocessSource({ cacheDir, sourceDir, createNodeId, + createNode, }) return contents diff --git a/packages/gatsby-plugin-static-image/src/watcher.ts b/packages/gatsby-plugin-static-image/src/watcher.ts new file mode 100644 index 0000000000000..747737b518c0b --- /dev/null +++ b/packages/gatsby-plugin-static-image/src/watcher.ts @@ -0,0 +1,86 @@ +import chokidar, { FSWatcher } from "chokidar" +import { + Actions, + ParentSpanPluginArgs, + GatsbyCache, + Reporter, + Node, +} from "gatsby" +import { createImageNode, IImageMetadata, writeImage } from "./image-processing" + +let watcher: FSWatcher | undefined + +/** + * Watch a static source image for changes + */ +export function watchImage({ + fullPath, + createNodeId, + createNode, + cache, + reporter, +}: { + fullPath: string + createNodeId: ParentSpanPluginArgs["createNodeId"] + createNode: Actions["createNode"] + cache: GatsbyCache + reporter: Reporter +}): void { + // We use a shared watcher, but only create it if needed + if (!watcher) { + watcher = chokidar.watch(fullPath) + watcher.on( + `change`, + async (path: string): Promise => { + reporter.verbose(`Image changed: ${path}`) + const node = await createImageNode({ + fullPath: path, + createNodeId, + createNode, + }) + if (!node) { + reporter.warn(`Could not process image ${path}`) + return + } + await updateImages({ node, cache, reporter }) + } + ) + } else { + // If we already have a watcher, just add this image to it + watcher.add(fullPath) + } +} +/** + * Update any static image instances that use a source image + */ +async function updateImages({ + cache, + node, + reporter, +}: { + cache: GatsbyCache + node: Node + reporter: Reporter +}): Promise { + // See if any static image instances use this source image file + const imageRefs: Record = await cache.get( + `ref-${node.id}` + ) + + if (!imageRefs) { + return + } + + await Promise.all( + Object.values(imageRefs).map( + async ({ isFixed, contentDigest, args, cacheFilename }) => { + if (contentDigest && contentDigest === node.internal.contentDigest) { + // Skipping, because the file is unchanged + return + } + // Update the image + await writeImage(node, args, reporter, cache, isFixed, cacheFilename) + } + ) + ) +} From 3242599ceef7d5208f9435cb4dcefc7cd4c8b40a Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 2 Oct 2020 11:41:35 +0100 Subject: [PATCH 022/143] Update readme --- packages/gatsby-plugin-static-image/README.md | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/gatsby-plugin-static-image/README.md b/packages/gatsby-plugin-static-image/README.md index ecb6a382d4625..8fb5dc47580c0 100644 --- a/packages/gatsby-plugin-static-image/README.md +++ b/packages/gatsby-plugin-static-image/README.md @@ -29,21 +29,23 @@ This component lets you write this instead: ```js import React from "react" -import { StaticImage as Img } from "gatsby-plugin-static-image" +import { StaticImage } from "gatsby-plugin-static-image" -export const Dino = () => T-Rex +export const Dino = () => ( + +) ``` -The `src` prop is an image `relativePath`, so you need to ensure it's in a folder that is parsed by gatsby-source-filesystem. +The `src` prop is relative to the source file, like in static HTML You can pass in options that match ones passed to the `ImageSharp` query: ```js import React from "react" -import { StaticImage as Img } from "gatsby-plugin-static-image" +import { StaticImage } from "gatsby-plugin-static-image" export const Dino = () => ( - { ## How does it work? -When your site is compiled, any references to StaticImage components are extracted, the images are resized by Sharp in a similar way to `gatsby-transformer-sharp`, and then the resulting sharp object is written to `.cache/caches/gatsby-plugin-static-image/`, with the filename generated as a hash of the normalized image props. Next, a Babel plugin finds any references to StaticImage, calculates the same hash, loads the JSON file with the sharp object, then adds it as a new `parsedValues` prop. It then returns a GatsbyImage, passing the parsedValues as the fixed or fluid prop. Errors don't cause the build to fail, but instead are written to the component as an `__error` prop, which is then logged in develop. +When your site is compiled, any references to StaticImage components are extracted, the images are resized by Sharp in a similar way to `gatsby-transformer-sharp`, and then the resulting sharp object is written to `.cache/caches/gatsby-plugin-static-image/`, with the filename generated as a hash of the normalized image props. Next, a Babel plugin finds any references to StaticImage, calculates the same hash, then adds a `require()` to that JSON file it as a new `parsedValues` prop. It then returns a GatsbyImage, passing the parsedValues as the fixed or fluid prop. Errors don't cause the build to fail, but instead are written to the component as an `__error` prop, which is then logged in develop. ### Are there restrictions to how this is used? @@ -101,7 +103,7 @@ The props must be able to be statically-analyzed at build time. You can't pass t } ``` -You can use variables and expressions if they're in the scope of the component, e.g.: +You can use variables and expressions if they're in the scope of the file, e.g.: ```js //OK @@ -113,8 +115,10 @@ You can use variables and expressions if they're in the scope of the component, ```js //Also OK + +const width = 300 + () => { - const width = 300 const height = width * 16 / 9 return } @@ -123,7 +127,7 @@ You can use variables and expressions if they're in the scope of the component, ## Installation ```bash -npm install gatsby-plugin-static-image +npm install gatsby@static-image gatsby-plugin-static-image@static-image ``` ...then add it to your `gatsby-config.js`: From 70616e2ffc14ea7dcf1c474e248782c7a4867c87 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 2 Oct 2020 12:21:38 +0100 Subject: [PATCH 023/143] Update deps --- packages/gatsby-plugin-static-image/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-plugin-static-image/package.json b/packages/gatsby-plugin-static-image/package.json index a420997d8f22b..2716d54ac79da 100644 --- a/packages/gatsby-plugin-static-image/package.json +++ b/packages/gatsby-plugin-static-image/package.json @@ -26,6 +26,7 @@ }, "peerDependencies": { "gatsby": ">=2", + "babel-plugin-remove-graphql-queries": "*", "gatsby-cli": "*", "gatsby-core-utils": "*", "gatsby-image": "*", @@ -35,7 +36,7 @@ "dependencies": { "@babel/parser": "^7.8.7", "@babel/traverse": "^7.8.6", - "babel-plugin-remove-graphql-queries": "^2.9.19", + "chokidar": "^3.4.2", "fs-extra": "^8.1.0", "babel-jsx-utils": "^1.0.1" }, From 67ea9ef862dc811c6f3cd01685a4705cdc675167 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 2 Oct 2020 15:46:37 +0100 Subject: [PATCH 024/143] Only watch in develop --- packages/gatsby-plugin-sharp/src/process-file.js | 1 + packages/gatsby-plugin-static-image/src/image-processing.ts | 6 ++++-- packages/gatsby-plugin-static-image/src/watcher.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/gatsby-plugin-sharp/src/process-file.js b/packages/gatsby-plugin-sharp/src/process-file.js index be5e84f53c371..1754f801dc7eb 100644 --- a/packages/gatsby-plugin-sharp/src/process-file.js +++ b/packages/gatsby-plugin-sharp/src/process-file.js @@ -74,6 +74,7 @@ exports.processFile = (file, transforms, options = {}) => { return transforms.map(async transform => { try { const { outputPath, args } = transform + console.log(`Start processing ${outputPath}`) debug(`Start processing ${outputPath}`) await fs.ensureDir(path.dirname(outputPath)) diff --git a/packages/gatsby-plugin-static-image/src/image-processing.ts b/packages/gatsby-plugin-static-image/src/image-processing.ts index 73058c00176be..207eda1099f1d 100644 --- a/packages/gatsby-plugin-static-image/src/image-processing.ts +++ b/packages/gatsby-plugin-static-image/src/image-processing.ts @@ -102,8 +102,10 @@ export async function writeImages({ await writeImage(file, args, reporter, cache, isFixed, cacheFilename) - // Watch the source image for changes - watchImage({ createNode, createNodeId, fullPath, cache, reporter }) + if (process.env.NODE_ENV === `development`) { + // Watch the source image for changes + watchImage({ createNode, createNodeId, fullPath, cache, reporter }) + } } ) diff --git a/packages/gatsby-plugin-static-image/src/watcher.ts b/packages/gatsby-plugin-static-image/src/watcher.ts index 747737b518c0b..be4e8cefb8a94 100644 --- a/packages/gatsby-plugin-static-image/src/watcher.ts +++ b/packages/gatsby-plugin-static-image/src/watcher.ts @@ -11,7 +11,7 @@ import { createImageNode, IImageMetadata, writeImage } from "./image-processing" let watcher: FSWatcher | undefined /** - * Watch a static source image for changes + * Watch a static source image for changes during develop */ export function watchImage({ fullPath, From 4f6836355052882880d577cae8afe723291cd9e6 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Mon, 5 Oct 2020 09:29:00 +0100 Subject: [PATCH 025/143] Rename to gatsby-plugin-image --- .../.babelrc | 0 .../README.md | 10 +++++----- .../gatsby-node.js | 0 .../index.js | 0 .../package.json | 6 +++--- .../src/babel-parse-to-ast.ts | 0 .../src/babel-plugin-parse-static-images.ts | 2 +- .../src/gatsby-node.ts | 7 +------ .../src/get-custom-sharp-fields.ts | 0 .../src/image-processing.ts | 0 .../src/index.ts | 0 .../src/parser.ts | 2 +- .../src/preprocess-source.ts | 9 ++------- .../src/static-image.tsx | 2 +- .../src/types.d.ts | 0 .../src/utils.ts | 0 .../src/watcher.ts | 0 .../tsconfig.json | 0 packages/gatsby/src/query/file-parser.js | 5 +---- 19 files changed, 15 insertions(+), 28 deletions(-) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/.babelrc (100%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/README.md (86%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/gatsby-node.js (100%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/index.js (100%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/package.json (91%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/babel-parse-to-ast.ts (100%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/babel-plugin-parse-static-images.ts (97%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/gatsby-node.ts (78%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/get-custom-sharp-fields.ts (100%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/image-processing.ts (100%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/index.ts (100%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/parser.ts (92%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/preprocess-source.ts (85%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/static-image.tsx (89%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/types.d.ts (100%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/utils.ts (100%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/src/watcher.ts (100%) rename packages/{gatsby-plugin-static-image => gatsby-plugin-image}/tsconfig.json (100%) diff --git a/packages/gatsby-plugin-static-image/.babelrc b/packages/gatsby-plugin-image/.babelrc similarity index 100% rename from packages/gatsby-plugin-static-image/.babelrc rename to packages/gatsby-plugin-image/.babelrc diff --git a/packages/gatsby-plugin-static-image/README.md b/packages/gatsby-plugin-image/README.md similarity index 86% rename from packages/gatsby-plugin-static-image/README.md rename to packages/gatsby-plugin-image/README.md index 8fb5dc47580c0..ccc3329e8737d 100644 --- a/packages/gatsby-plugin-static-image/README.md +++ b/packages/gatsby-plugin-image/README.md @@ -29,7 +29,7 @@ This component lets you write this instead: ```js import React from "react" -import { StaticImage } from "gatsby-plugin-static-image" +import { StaticImage } from "gatsby-plugin-image" export const Dino = () => ( @@ -42,7 +42,7 @@ You can pass in options that match ones passed to the `ImageSharp` query: ```js import React from "react" -import { StaticImage } from "gatsby-plugin-static-image" +import { StaticImage } from "gatsby-plugin-image" export const Dino = () => ( { ## How does it work? -When your site is compiled, any references to StaticImage components are extracted, the images are resized by Sharp in a similar way to `gatsby-transformer-sharp`, and then the resulting sharp object is written to `.cache/caches/gatsby-plugin-static-image/`, with the filename generated as a hash of the normalized image props. Next, a Babel plugin finds any references to StaticImage, calculates the same hash, then adds a `require()` to that JSON file it as a new `parsedValues` prop. It then returns a GatsbyImage, passing the parsedValues as the fixed or fluid prop. Errors don't cause the build to fail, but instead are written to the component as an `__error` prop, which is then logged in develop. +When your site is compiled, any references to StaticImage components are extracted, the images are resized by Sharp in a similar way to `gatsby-transformer-sharp`, and then the resulting sharp object is written to `.cache/caches/gatsby-plugin-image/`, with the filename generated as a hash of the normalized image props. Next, a Babel plugin finds any references to StaticImage, calculates the same hash, then adds a `require()` to that JSON file it as a new `parsedValues` prop. It then returns a GatsbyImage, passing the parsedValues as the fixed or fluid prop. Errors don't cause the build to fail, but instead are written to the component as an `__error` prop, which is then logged in develop. ### Are there restrictions to how this is used? @@ -127,7 +127,7 @@ const width = 300 ## Installation ```bash -npm install gatsby@static-image gatsby-plugin-static-image@static-image +npm install gatsby@static-image gatsby-plugin-image@static-image ``` ...then add it to your `gatsby-config.js`: @@ -137,7 +137,7 @@ module.exports = { //... plugins: [ "gatsby-plugin-sharp", - "gatsby-plugin-static-image", + "gatsby-plugin-image", //... ], } diff --git a/packages/gatsby-plugin-static-image/gatsby-node.js b/packages/gatsby-plugin-image/gatsby-node.js similarity index 100% rename from packages/gatsby-plugin-static-image/gatsby-node.js rename to packages/gatsby-plugin-image/gatsby-node.js diff --git a/packages/gatsby-plugin-static-image/index.js b/packages/gatsby-plugin-image/index.js similarity index 100% rename from packages/gatsby-plugin-static-image/index.js rename to packages/gatsby-plugin-image/index.js diff --git a/packages/gatsby-plugin-static-image/package.json b/packages/gatsby-plugin-image/package.json similarity index 91% rename from packages/gatsby-plugin-static-image/package.json rename to packages/gatsby-plugin-image/package.json index 2716d54ac79da..a81608642a985 100644 --- a/packages/gatsby-plugin-static-image/package.json +++ b/packages/gatsby-plugin-image/package.json @@ -1,5 +1,5 @@ { - "name": "gatsby-plugin-static-image", + "name": "gatsby-plugin-image", "version": "1.0.0", "scripts": { "build": "babel src --out-dir dist/ --ignore \"**/__tests__\" --ignore \"**/__mocks__\" --extensions \".ts,.tsx\"", @@ -13,7 +13,7 @@ "gatsby-node.js" ], "types": "dist/index.d.ts", - "homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-static-image#readme", + "homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-image#readme", "devDependencies": { "@types/node": "^14.10.2", "@babel/cli": "^7.8.7", @@ -44,7 +44,7 @@ "repository": { "type": "git", "url": "https://github.com/gatsbyjs/gatsby.git", - "directory": "packages/gatsby-plugin-static-image" + "directory": "packages/gatsby-plugin-image" }, "author": "Matt Kane ", "license": "MIT" diff --git a/packages/gatsby-plugin-static-image/src/babel-parse-to-ast.ts b/packages/gatsby-plugin-image/src/babel-parse-to-ast.ts similarity index 100% rename from packages/gatsby-plugin-static-image/src/babel-parse-to-ast.ts rename to packages/gatsby-plugin-image/src/babel-parse-to-ast.ts diff --git a/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts b/packages/gatsby-plugin-image/src/babel-plugin-parse-static-images.ts similarity index 97% rename from packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts rename to packages/gatsby-plugin-image/src/babel-plugin-parse-static-images.ts index 6f4e0994585df..3c53b85bf3d02 100644 --- a/packages/gatsby-plugin-static-image/src/babel-plugin-parse-static-images.ts +++ b/packages/gatsby-plugin-image/src/babel-plugin-parse-static-images.ts @@ -22,7 +22,7 @@ export default function attrs({ if ( !nodePath .get(`name`) - .referencesImport(`gatsby-plugin-static-image`, `StaticImage`) + .referencesImport(`gatsby-plugin-image`, `StaticImage`) ) { return } diff --git a/packages/gatsby-plugin-static-image/src/gatsby-node.ts b/packages/gatsby-plugin-image/src/gatsby-node.ts similarity index 78% rename from packages/gatsby-plugin-static-image/src/gatsby-node.ts rename to packages/gatsby-plugin-image/src/gatsby-node.ts index 0d59997c1eb03..5bfbffc4ec2cd 100644 --- a/packages/gatsby-plugin-static-image/src/gatsby-node.ts +++ b/packages/gatsby-plugin-image/src/gatsby-node.ts @@ -9,12 +9,7 @@ export const onCreateBabelConfig: GatsbyNode["onCreateBabelConfig"] = ({ }) => { const root = store.getState().program.directory - const cacheDir = path.join( - root, - `.cache`, - `caches`, - `gatsby-plugin-static-image` - ) + const cacheDir = path.join(root, `.cache`, `caches`, `gatsby-plugin-image`) actions.setBabelPlugin({ name: require.resolve(`./babel-plugin-parse-static-images`), diff --git a/packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts b/packages/gatsby-plugin-image/src/get-custom-sharp-fields.ts similarity index 100% rename from packages/gatsby-plugin-static-image/src/get-custom-sharp-fields.ts rename to packages/gatsby-plugin-image/src/get-custom-sharp-fields.ts diff --git a/packages/gatsby-plugin-static-image/src/image-processing.ts b/packages/gatsby-plugin-image/src/image-processing.ts similarity index 100% rename from packages/gatsby-plugin-static-image/src/image-processing.ts rename to packages/gatsby-plugin-image/src/image-processing.ts diff --git a/packages/gatsby-plugin-static-image/src/index.ts b/packages/gatsby-plugin-image/src/index.ts similarity index 100% rename from packages/gatsby-plugin-static-image/src/index.ts rename to packages/gatsby-plugin-image/src/index.ts diff --git a/packages/gatsby-plugin-static-image/src/parser.ts b/packages/gatsby-plugin-image/src/parser.ts similarity index 92% rename from packages/gatsby-plugin-static-image/src/parser.ts rename to packages/gatsby-plugin-image/src/parser.ts index e0627820ab95b..b391812c60213 100644 --- a/packages/gatsby-plugin-static-image/src/parser.ts +++ b/packages/gatsby-plugin-image/src/parser.ts @@ -18,7 +18,7 @@ export const extractStaticImageProps = ( if ( !nodePath .get(`name`) - .referencesImport(`gatsby-plugin-static-image`, `StaticImage`) + .referencesImport(`gatsby-plugin-image`, `StaticImage`) ) { return } diff --git a/packages/gatsby-plugin-static-image/src/preprocess-source.ts b/packages/gatsby-plugin-image/src/preprocess-source.ts similarity index 85% rename from packages/gatsby-plugin-static-image/src/preprocess-source.ts rename to packages/gatsby-plugin-image/src/preprocess-source.ts index 0d83ea4f81a41..4243784ca8c58 100644 --- a/packages/gatsby-plugin-static-image/src/preprocess-source.ts +++ b/packages/gatsby-plugin-image/src/preprocess-source.ts @@ -16,19 +16,14 @@ export async function preprocessSource({ }: PreprocessSourceArgs): Promise { if ( !contents.includes(`StaticImage`) || - !contents.includes(`gatsby-plugin-static-image`) || + !contents.includes(`gatsby-plugin-image`) || !extensions.includes(path.extname(filename)) ) { return contents } const root = store.getState().program.directory - const cacheDir = path.join( - root, - `.cache`, - `caches`, - `gatsby-plugin-static-image` - ) + const cacheDir = path.join(root, `.cache`, `caches`, `gatsby-plugin-image`) const ast = babelParseToAst(contents, filename) diff --git a/packages/gatsby-plugin-static-image/src/static-image.tsx b/packages/gatsby-plugin-image/src/static-image.tsx similarity index 89% rename from packages/gatsby-plugin-static-image/src/static-image.tsx rename to packages/gatsby-plugin-image/src/static-image.tsx index 5c109f2c9e163..154fa98a86a2f 100644 --- a/packages/gatsby-plugin-static-image/src/static-image.tsx +++ b/packages/gatsby-plugin-image/src/static-image.tsx @@ -32,7 +32,7 @@ export const StaticImage: React.FC = ({ console.warn(`Image not loaded`, src) if (!__error && process.env.NODE_ENV === `development`) { console.warn( - `Please ensure that "gatsby-plugin-static-image" is included in the plugins array in gatsby-config.js` + `Please ensure that "gatsby-plugin-image" is included in the plugins array in gatsby-config.js` ) } return null diff --git a/packages/gatsby-plugin-static-image/src/types.d.ts b/packages/gatsby-plugin-image/src/types.d.ts similarity index 100% rename from packages/gatsby-plugin-static-image/src/types.d.ts rename to packages/gatsby-plugin-image/src/types.d.ts diff --git a/packages/gatsby-plugin-static-image/src/utils.ts b/packages/gatsby-plugin-image/src/utils.ts similarity index 100% rename from packages/gatsby-plugin-static-image/src/utils.ts rename to packages/gatsby-plugin-image/src/utils.ts diff --git a/packages/gatsby-plugin-static-image/src/watcher.ts b/packages/gatsby-plugin-image/src/watcher.ts similarity index 100% rename from packages/gatsby-plugin-static-image/src/watcher.ts rename to packages/gatsby-plugin-image/src/watcher.ts diff --git a/packages/gatsby-plugin-static-image/tsconfig.json b/packages/gatsby-plugin-image/tsconfig.json similarity index 100% rename from packages/gatsby-plugin-static-image/tsconfig.json rename to packages/gatsby-plugin-image/tsconfig.json diff --git a/packages/gatsby/src/query/file-parser.js b/packages/gatsby/src/query/file-parser.js index 0f8db930ca874..ec051d8ff0ea1 100644 --- a/packages/gatsby/src/query/file-parser.js +++ b/packages/gatsby/src/query/file-parser.js @@ -418,10 +418,7 @@ export default class FileParser { return null } - if ( - !text.includes(`graphql`) && - !text.includes(`gatsby-plugin-static-image`) - ) + if (!text.includes(`graphql`) && !text.includes(`gatsby-plugin-image`)) return null const hash = crypto .createHash(`md5`) From f5dc9f75e61e5299929952bda66187cc9c5dfa7d Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Mon, 5 Oct 2020 09:42:02 +0100 Subject: [PATCH 026/143] Rearrange, ready for merger --- .../src/{ => components}/static-image.tsx | 2 +- .../gatsby-plugin-image/src/gatsby-node.ts | 2 +- packages/gatsby-plugin-image/src/index.ts | 2 +- .../get-custom-sharp-fields.ts | 2 +- .../src/{ => node-apis}/image-processing.ts | 2 +- .../parser.ts} | 33 +++++++++++++++++++ .../src/{ => node-apis}/preprocess-source.ts | 2 +- .../src/{ => node-apis}/watcher.ts | 0 packages/gatsby-plugin-image/src/parser.ts | 33 ------------------- 9 files changed, 39 insertions(+), 39 deletions(-) rename packages/gatsby-plugin-image/src/{ => components}/static-image.tsx (95%) rename packages/gatsby-plugin-image/src/{ => node-apis}/get-custom-sharp-fields.ts (97%) rename packages/gatsby-plugin-image/src/{ => node-apis}/image-processing.ts (98%) rename packages/gatsby-plugin-image/src/{babel-parse-to-ast.ts => node-apis/parser.ts} (58%) rename packages/gatsby-plugin-image/src/{ => node-apis}/preprocess-source.ts (94%) rename packages/gatsby-plugin-image/src/{ => node-apis}/watcher.ts (100%) delete mode 100644 packages/gatsby-plugin-image/src/parser.ts diff --git a/packages/gatsby-plugin-image/src/static-image.tsx b/packages/gatsby-plugin-image/src/components/static-image.tsx similarity index 95% rename from packages/gatsby-plugin-image/src/static-image.tsx rename to packages/gatsby-plugin-image/src/components/static-image.tsx index 154fa98a86a2f..dd55bed7bea1a 100644 --- a/packages/gatsby-plugin-image/src/static-image.tsx +++ b/packages/gatsby-plugin-image/src/components/static-image.tsx @@ -1,5 +1,5 @@ import React from "react" -import { splitProps, AllProps } from "./utils" +import { splitProps, AllProps } from "../utils" import Image, { FluidObject, FixedObject } from "gatsby-image" // These values are added by Babel. Do not add them manually diff --git a/packages/gatsby-plugin-image/src/gatsby-node.ts b/packages/gatsby-plugin-image/src/gatsby-node.ts index 5bfbffc4ec2cd..74f9e332b2f07 100644 --- a/packages/gatsby-plugin-image/src/gatsby-node.ts +++ b/packages/gatsby-plugin-image/src/gatsby-node.ts @@ -1,4 +1,4 @@ -export * from "./preprocess-source" +export * from "./node-apis/preprocess-source" import { GatsbyNode } from "gatsby" import path from "path" diff --git a/packages/gatsby-plugin-image/src/index.ts b/packages/gatsby-plugin-image/src/index.ts index bebf9ca03f80d..54218dcfc5cb2 100644 --- a/packages/gatsby-plugin-image/src/index.ts +++ b/packages/gatsby-plugin-image/src/index.ts @@ -1 +1 @@ -export { StaticImage } from "./static-image" +export { StaticImage } from "./components/static-image" diff --git a/packages/gatsby-plugin-image/src/get-custom-sharp-fields.ts b/packages/gatsby-plugin-image/src/node-apis/get-custom-sharp-fields.ts similarity index 97% rename from packages/gatsby-plugin-image/src/get-custom-sharp-fields.ts rename to packages/gatsby-plugin-image/src/node-apis/get-custom-sharp-fields.ts index d851cecb0fdfa..3f0da73f81386 100644 --- a/packages/gatsby-plugin-image/src/get-custom-sharp-fields.ts +++ b/packages/gatsby-plugin-image/src/node-apis/get-custom-sharp-fields.ts @@ -1,6 +1,6 @@ import { Node, GatsbyCache, Reporter } from "gatsby" import { fluid, fixed, traceSVG } from "gatsby-plugin-sharp" -import { SharpProps } from "./utils" +import { SharpProps } from "../utils" /** * By default the gatsby-plugin-sharp functions don't create webP or SVG diff --git a/packages/gatsby-plugin-image/src/image-processing.ts b/packages/gatsby-plugin-image/src/node-apis/image-processing.ts similarity index 98% rename from packages/gatsby-plugin-image/src/image-processing.ts rename to packages/gatsby-plugin-image/src/node-apis/image-processing.ts index 207eda1099f1d..0461b34a92df2 100644 --- a/packages/gatsby-plugin-image/src/image-processing.ts +++ b/packages/gatsby-plugin-image/src/node-apis/image-processing.ts @@ -9,7 +9,7 @@ import { fluid as fluidSharp, fixed as fixedSharp } from "gatsby-plugin-sharp" import { createFileNode } from "gatsby-source-filesystem/create-file-node" import fs from "fs-extra" import path from "path" -import { ImageProps, SharpProps } from "./utils" +import { ImageProps, SharpProps } from "../utils" import { getCustomSharpFields } from "./get-custom-sharp-fields" import { watchImage } from "./watcher" diff --git a/packages/gatsby-plugin-image/src/babel-parse-to-ast.ts b/packages/gatsby-plugin-image/src/node-apis/parser.ts similarity index 58% rename from packages/gatsby-plugin-image/src/babel-parse-to-ast.ts rename to packages/gatsby-plugin-image/src/node-apis/parser.ts index e1b2131e81e8e..4d5ccad1e038f 100644 --- a/packages/gatsby-plugin-image/src/babel-parse-to-ast.ts +++ b/packages/gatsby-plugin-image/src/node-apis/parser.ts @@ -1,3 +1,7 @@ +import traverse from "@babel/traverse" +import { hashOptions, evaluateImageAttributes, ImageProps } from "../utils" +import { NodePath } from "@babel/core" +import { JSXOpeningElement } from "@babel/types" import { parse, ParserOptions } from "@babel/parser" import babel from "@babel/core" @@ -62,3 +66,32 @@ export function babelParseToAst( ): babel.types.File { return parse(contents, getBabelParserOptions(filePath)) } + +/** + * Traverses the parsed source, looking for StaticImage components. + * Extracts and returns the props from any that are found + */ +export const extractStaticImageProps = ( + ast: babel.types.File +): Map => { + const images: Map = new Map() + + traverse(ast, { + JSXOpeningElement(nodePath) { + // Is this a StaticImage? + if ( + !nodePath + .get(`name`) + .referencesImport(`gatsby-plugin-image`, `StaticImage`) + ) { + return + } + const image = evaluateImageAttributes( + // There's a conflict between the definition of NodePath in @babel/core and @babel/traverse + (nodePath as unknown) as NodePath + ) as ImageProps + images.set(hashOptions(image), image) + }, + }) + return images +} diff --git a/packages/gatsby-plugin-image/src/preprocess-source.ts b/packages/gatsby-plugin-image/src/node-apis/preprocess-source.ts similarity index 94% rename from packages/gatsby-plugin-image/src/preprocess-source.ts rename to packages/gatsby-plugin-image/src/node-apis/preprocess-source.ts index 4243784ca8c58..a94c66ff3d525 100644 --- a/packages/gatsby-plugin-image/src/preprocess-source.ts +++ b/packages/gatsby-plugin-image/src/node-apis/preprocess-source.ts @@ -1,5 +1,5 @@ import { PreprocessSourceArgs } from "gatsby" -import { babelParseToAst } from "./babel-parse-to-ast" +import { babelParseToAst } from "./parser" import path from "path" import { extractStaticImageProps } from "./parser" import { writeImages } from "./image-processing" diff --git a/packages/gatsby-plugin-image/src/watcher.ts b/packages/gatsby-plugin-image/src/node-apis/watcher.ts similarity index 100% rename from packages/gatsby-plugin-image/src/watcher.ts rename to packages/gatsby-plugin-image/src/node-apis/watcher.ts diff --git a/packages/gatsby-plugin-image/src/parser.ts b/packages/gatsby-plugin-image/src/parser.ts deleted file mode 100644 index b391812c60213..0000000000000 --- a/packages/gatsby-plugin-image/src/parser.ts +++ /dev/null @@ -1,33 +0,0 @@ -import traverse from "@babel/traverse" -import { hashOptions, evaluateImageAttributes, ImageProps } from "./utils" -import { NodePath } from "@babel/core" -import { JSXOpeningElement } from "@babel/types" - -/** - * Traverses the parsed source, looking for StaticImage components. - * Extracts and returns the props from any that are found - */ -export const extractStaticImageProps = ( - ast: babel.types.File -): Map => { - const images: Map = new Map() - - traverse(ast, { - JSXOpeningElement(nodePath) { - // Is this a StaticImage? - if ( - !nodePath - .get(`name`) - .referencesImport(`gatsby-plugin-image`, `StaticImage`) - ) { - return - } - const image = evaluateImageAttributes( - // There's a conflict between the definition of NodePath in @babel/core and @babel/traverse - (nodePath as unknown) as NodePath - ) as ImageProps - images.set(hashOptions(image), image) - }, - }) - return images -} From 13e1ca83ee263e9b03f657381f52e6fa549f987c Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Mon, 5 Oct 2020 09:58:42 +0100 Subject: [PATCH 027/143] Import @wardpeet 's gatsby-image-netxtgen --- packages/gatsby-plugin-image/gatsby-ssr.js | 2 + .../macros/cssnano.macro.js | 30 +++ .../macros/terser.macro.js | 28 +++ .../gatsby-plugin-image/src/compat.browser.js | 1 + .../src/compat.browser.modern.js | 1 + .../src/compat.browser.module.js | 1 + packages/gatsby-plugin-image/src/compat.js | 1 + .../src/components/gatsby-image.browser.tsx | 181 +++++++++++++++ .../src/components/gatsby-image.server.tsx | 84 +++++++ .../src/components/hooks.ts | 210 ++++++++++++++++++ .../src/components/intersection-observer.ts | 39 ++++ .../src/components/layout-wrapper.tsx | 79 +++++++ .../src/components/lazy-hydrate.tsx | 80 +++++++ .../src/components/main-image.tsx | 14 ++ .../src/components/picture.tsx | 130 +++++++++++ .../src/components/placeholder.tsx | 42 ++++ .../gatsby-plugin-image/src/gatsby-ssr.tsx | 84 +++++++ packages/gatsby-plugin-image/src/global.d.ts | 11 + .../gatsby-plugin-image/src/index.browser.ts | 4 + packages/gatsby-plugin-image/src/index.ts | 3 + 20 files changed, 1025 insertions(+) create mode 100644 packages/gatsby-plugin-image/gatsby-ssr.js create mode 100644 packages/gatsby-plugin-image/macros/cssnano.macro.js create mode 100644 packages/gatsby-plugin-image/macros/terser.macro.js create mode 100644 packages/gatsby-plugin-image/src/compat.browser.js create mode 100644 packages/gatsby-plugin-image/src/compat.browser.modern.js create mode 100644 packages/gatsby-plugin-image/src/compat.browser.module.js create mode 100644 packages/gatsby-plugin-image/src/compat.js create mode 100644 packages/gatsby-plugin-image/src/components/gatsby-image.browser.tsx create mode 100644 packages/gatsby-plugin-image/src/components/gatsby-image.server.tsx create mode 100644 packages/gatsby-plugin-image/src/components/hooks.ts create mode 100644 packages/gatsby-plugin-image/src/components/intersection-observer.ts create mode 100644 packages/gatsby-plugin-image/src/components/layout-wrapper.tsx create mode 100644 packages/gatsby-plugin-image/src/components/lazy-hydrate.tsx create mode 100644 packages/gatsby-plugin-image/src/components/main-image.tsx create mode 100644 packages/gatsby-plugin-image/src/components/picture.tsx create mode 100644 packages/gatsby-plugin-image/src/components/placeholder.tsx create mode 100644 packages/gatsby-plugin-image/src/gatsby-ssr.tsx create mode 100644 packages/gatsby-plugin-image/src/global.d.ts create mode 100644 packages/gatsby-plugin-image/src/index.browser.ts diff --git a/packages/gatsby-plugin-image/gatsby-ssr.js b/packages/gatsby-plugin-image/gatsby-ssr.js new file mode 100644 index 0000000000000..e28cad37e6fe2 --- /dev/null +++ b/packages/gatsby-plugin-image/gatsby-ssr.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +module.exports = require("./dist/gatsby-ssr"); diff --git a/packages/gatsby-plugin-image/macros/cssnano.macro.js b/packages/gatsby-plugin-image/macros/cssnano.macro.js new file mode 100644 index 0000000000000..22c8dcd4ed05b --- /dev/null +++ b/packages/gatsby-plugin-image/macros/cssnano.macro.js @@ -0,0 +1,30 @@ +const { createMacro } = require(`babel-plugin-macros`); +const printAST = require(`ast-pretty-print`); +const { doSync } = require(`do-sync`); + +module.exports = createMacro(cssNanoMacro); + +const syncMinify = doSync((code, options = {}) => { + const postcss = require(`postcss`); + + return postcss({ + plugins: [ + require(`cssnano`)({ + preset: [`default`, { discardComments: { removeAll: true } }], + }), + ], + }).process(code, { from: undefined, to: undefined }); +}); + +function cssNanoMacro({ references, state, babel }) { + references.default.forEach((referencePath) => { + if (referencePath.parentPath.type === `TaggedTemplateExpression`) { + const quasiPath = referencePath.parentPath.get(`quasi`); + const string = quasiPath.parentPath.get(`quasi`).evaluate().value; + + const result = syncMinify(string); + + quasiPath.parentPath.replaceWithSourceString(`\`${result.css}\``); + } + }); +} diff --git a/packages/gatsby-plugin-image/macros/terser.macro.js b/packages/gatsby-plugin-image/macros/terser.macro.js new file mode 100644 index 0000000000000..c3dffc6b8064d --- /dev/null +++ b/packages/gatsby-plugin-image/macros/terser.macro.js @@ -0,0 +1,28 @@ +const { createMacro } = require(`babel-plugin-macros`); +const printAST = require(`ast-pretty-print`); +const { doSync } = require(`do-sync`); + +module.exports = createMacro(terserMacro); + +const syncMinify = doSync((code, options = {}) => { + const { minify } = require(`terser`); + + return minify(code, options); +}); + +function terserMacro({ references, state, babel }) { + references.default.forEach((referencePath) => { + if (referencePath.parentPath.type === `TaggedTemplateExpression`) { + const quasiPath = referencePath.parentPath.get(`quasi`); + const string = quasiPath.parentPath.get(`quasi`).evaluate().value; + + const result = syncMinify(string, { + mangle: { + toplevel: true, + }, + }); + + quasiPath.parentPath.replaceWithSourceString(`\`${result.code}\``); + } + }); +} diff --git a/packages/gatsby-plugin-image/src/compat.browser.js b/packages/gatsby-plugin-image/src/compat.browser.js new file mode 100644 index 0000000000000..f8bfce3e48254 --- /dev/null +++ b/packages/gatsby-plugin-image/src/compat.browser.js @@ -0,0 +1 @@ +export * from "./dist/gatsby-image.compat.browser" diff --git a/packages/gatsby-plugin-image/src/compat.browser.modern.js b/packages/gatsby-plugin-image/src/compat.browser.modern.js new file mode 100644 index 0000000000000..8fb5f6ffbeb7e --- /dev/null +++ b/packages/gatsby-plugin-image/src/compat.browser.modern.js @@ -0,0 +1 @@ +export * from "./dist/gatsby-image.compat.browser.modern" diff --git a/packages/gatsby-plugin-image/src/compat.browser.module.js b/packages/gatsby-plugin-image/src/compat.browser.module.js new file mode 100644 index 0000000000000..f3262fd5f0a30 --- /dev/null +++ b/packages/gatsby-plugin-image/src/compat.browser.module.js @@ -0,0 +1 @@ +export * from "./dist/gatsby-image.compat.browser.module" diff --git a/packages/gatsby-plugin-image/src/compat.js b/packages/gatsby-plugin-image/src/compat.js new file mode 100644 index 0000000000000..bba870070f87b --- /dev/null +++ b/packages/gatsby-plugin-image/src/compat.js @@ -0,0 +1 @@ +export * from "./dist/gatsby-image.compat" diff --git a/packages/gatsby-plugin-image/src/components/gatsby-image.browser.tsx b/packages/gatsby-plugin-image/src/components/gatsby-image.browser.tsx new file mode 100644 index 0000000000000..a3f8084e978b8 --- /dev/null +++ b/packages/gatsby-plugin-image/src/components/gatsby-image.browser.tsx @@ -0,0 +1,181 @@ +import React, { + ElementType, + useEffect, + useRef, + FunctionComponent, + ImgHTMLAttributes, + useState, +} from "react" +import { + getWrapperProps, + hasNativeLazyLoadSupport, + storeImageloaded, +} from "./hooks" +import { LayoutWrapperProps } from "./layout-wrapper" +import { PlaceholderProps } from "./placeholder" +import { MainImageProps } from "./main-image" + +export type GatsbyImageProps = Omit< + ImgHTMLAttributes, + "placeholder" +> & { + alt: string + as?: ElementType + layout: LayoutWrapperProps["layout"] + className?: string + height?: number + images: Pick + placeholder: Pick + width?: number + onLoad?: Function + onError?: Function + onStartLoad?: Function +} + +let showedWarning = false + +export const GatsbyImageHydrator: FunctionComponent = function GatsbyImageHydrator({ + as: Type = `div`, + style, + className, + layout = `fixed`, + width, + height, + images, + onStartLoad, + onLoad: customOnLoad, + ...props +}) { + const root = useRef() + const hydrated = useRef(false) + const unobserveRef = useRef(null) + const lazyHydrator = useRef(null) + const ref = useRef() + const [isLoading, toggleIsLoading] = useState(hasNativeLazyLoadSupport) + const [isLoaded, toggleIsLoaded] = useState(false) + + if (!global.GATSBY___IMAGE && !showedWarning) { + showedWarning = true + console.warn( + `[gatsby-image] You're missing out on some cool performance features. Please add "gatsby-image" to your gatsby-config.js` + ) + } + + const { style: wStyle, className: wClass, ...wrapperProps } = getWrapperProps( + width, + height, + layout + ) + + useEffect(() => { + if (root.current) { + const hasSSRHtml = root.current.querySelector(`[data-gatsby-image-ssr]`) + + // when SSR and native lazyload is supported we'll do nothing ;) + if (hasNativeLazyLoadSupport && hasSSRHtml && global.GATSBY___IMAGE) { + onStartLoad && onStartLoad({ wasCached: false }) + + if ((hasSSRHtml as HTMLImageElement).complete) { + customOnLoad && (customOnLoad as Function)() + storeImageloaded(JSON.stringify(images)) + } + hasSSRHtml.addEventListener(`load`, function onLoad(e) { + hasSSRHtml.removeEventListener(`load`, onLoad) + + customOnLoad && (customOnLoad as Function)() + storeImageloaded(JSON.stringify(images)) + }) + return + } + + // Fallback to custom lazy loading (intersection observer) + import(`./intersection-observer`).then( + ({ createIntersectionObserver }) => { + const intersectionObserver = createIntersectionObserver(() => { + if (root.current) { + onStartLoad && onStartLoad({ wasCached: false }) + toggleIsLoading(true) + } + }) + + if (root.current) { + unobserveRef.current = intersectionObserver(root) + } + } + ) + } + + return () => { + if (unobserveRef.current) { + unobserveRef.current(root) + + // on unmount, make sure we cleanup + if (hydrated.current && lazyHydrator.current) { + lazyHydrator.current() + } + } + } + }, []) + + useEffect(() => { + if (root.current) { + const hasSSRHtml = root.current.querySelector(`[data-gatsby-image-ssr]`) + // On first server hydration do nothing + if (hasNativeLazyLoadSupport && hasSSRHtml && !hydrated.current) { + return + } + + import(`./lazy-hydrate`).then(({ lazyHydrate }) => { + lazyHydrator.current = lazyHydrate( + { + layout, + width, + height, + images, + isLoading, + isLoaded, + toggleIsLoaded: () => { + customOnLoad && (customOnLoad as Function)() + toggleIsLoaded(true) + }, + ref, + ...props, + }, + root, + hydrated + ) + }) + } + }, [ + width, + height, + layout, + images, + isLoading, + isLoaded, + toggleIsLoaded, + ref, + props, + ]) + + return ( + + ) +} + +export const GatsbyImage: FunctionComponent = function GatsbyImage( + props +) { + return +} +GatsbyImage.displayName = `GatsbyImage` diff --git a/packages/gatsby-plugin-image/src/components/gatsby-image.server.tsx b/packages/gatsby-plugin-image/src/components/gatsby-image.server.tsx new file mode 100644 index 0000000000000..43c0ddf25ea79 --- /dev/null +++ b/packages/gatsby-plugin-image/src/components/gatsby-image.server.tsx @@ -0,0 +1,84 @@ +import React, { + createElement, + ElementType, + FunctionComponent, + CSSProperties, +} from "react" +import { GatsbyImageProps } from "./gatsby-image.browser" +import { getWrapperProps, getMainProps, getPlaceHolderProps } from "./hooks" +import { Placeholder } from "./placeholder" +import { MainImage, MainImageProps } from "./main-image" +import { LayoutWrapper } from "./layout-wrapper" + +const removeNewLines = (str: string): string => str.replace(/\n/g, ``) + +export const GatsbyImageHydrator: FunctionComponent<{ + as?: ElementType + style?: CSSProperties + className?: string +}> = function GatsbyImageHydrator({ as: Type = `div`, children, ...props }) { + return {children} +} + +export const GatsbyImage: FunctionComponent = function GatsbyImage({ + as, + className, + style, + placeholder, + images, + width, + height, + layout = `fixed`, + loading = `lazy`, + ...props +}) { + const { style: wStyle, className: wClass, ...wrapperProps } = getWrapperProps( + width, + height, + layout + ) + + const cleanedImages: GatsbyImageProps["images"] = { + fallback: null, + sources: [], + } + if (images.fallback) { + cleanedImages.fallback = { + src: images.fallback.src, + srcSet: images.fallback.srcSet + ? removeNewLines(images.fallback.srcSet) + : null, + } + } + + if (images.sources) { + cleanedImages.sources = images.sources.map(source => { + return { + ...source, + srcSet: removeNewLines(source.srcSet), + } + }) + } + + return ( + + + {placeholder && } + )} + // When eager is set we want to start the isLoading state on true (we want to load the img without react) + {...getMainProps(loading === `eager`, false, cleanedImages, loading)} + /> + + + ) +} diff --git a/packages/gatsby-plugin-image/src/components/hooks.ts b/packages/gatsby-plugin-image/src/components/hooks.ts new file mode 100644 index 0000000000000..5af597f40331f --- /dev/null +++ b/packages/gatsby-plugin-image/src/components/hooks.ts @@ -0,0 +1,210 @@ +import { useState, CSSProperties, useEffect, useRef, RefObject } from "react" +const imageCache = new Set() + +// Native lazy-loading support: https://addyosmani.com/blog/lazy-loading/ +export const hasNativeLazyLoadSupport = + typeof HTMLImageElement !== `undefined` && + `loading` in HTMLImageElement.prototype + +export function storeImageloaded(cacheKey: string): void { + imageCache.add(cacheKey) +} + +export function hasImageLoaded(cacheKey: string): boolean { + return imageCache.has(cacheKey) +} + +export function getWrapperProps( + width: number, + height: number, + layout: "intrinsic" | "responsive" | "fixed" +) { + const wrapperStyle: CSSProperties = { + position: `relative`, + } + + if (layout === `fixed`) { + wrapperStyle.width = width + wrapperStyle.height = height + } + + if (layout === `intrinsic`) { + wrapperStyle.display = `inline-block` + } + + return { + className: `gatsby-image`, + style: wrapperStyle, + } +} + +export function getMainProps( + isLoading: boolean, + isLoaded: boolean, + images: any, + loading: "eager" | "lazy", + toggleLoaded?: any, + cacheKey?: string, + ref?: any +): any { + const result = { + ...images, + loading, + shouldLoad: isLoading, + "data-main-image": ``, + style: { + opacity: isLoaded ? 1 : 0, + }, + onLoad: function (e: any) { + if (isLoaded) { + return + } + + storeImageloaded(cacheKey) + + const target = e.target + const img = new Image() + img.src = target.currentSrc + + if (img.decode) { + // Decode the image through javascript to support our transition + img + .decode() + .catch(err => { + // ignore error, we just go forward + }) + .then(() => { + toggleLoaded(true) + }) + } else { + toggleLoaded(true) + } + }, + ref, + } + + // @ts-ignore + if (!global.GATSBY___IMAGE) { + result.style.height = `100%` + result.style.left = 0 + result.style.position = `absolute` + result.style.top = 0 + result.style.transform = `translateZ(0)` + result.style.transition = `opacity 500ms linear` + result.style.width = `100%` + result.style.willChange = `opacity` + } + + return result +} + +export function getPlaceHolderProps(placeholder: any) { + const result = { + ...placeholder, + "aria-hidden": true, + } + + // @ts-ignore + if (!global.GATSBY___IMAGE) { + result.style = { + height: `100%`, + left: 0, + position: `absolute`, + top: 0, + width: `100%`, + } + } + + return result +} + +export function useImageLoaded( + cacheKey: string, + loading: "lazy" | "eager", + ref: any +) { + const [isLoaded, toggleLoaded] = useState(false) + const [isLoading, toggleIsLoading] = useState(loading === `eager`) + + const rAF = + typeof window !== `undefined` && `requestAnimationFrame` in window + ? requestAnimationFrame + : function (cb: Function) { + return setTimeout(cb, 16) + } + const cRAF = + typeof window !== `undefined` && `cancelAnimationFrame` in window + ? cancelAnimationFrame + : clearTimeout + + useEffect(() => { + let interval: any + // @see https://stackoverflow.com/questions/44074747/componentdidmount-called-before-ref-callback/50019873#50019873 + function toggleIfRefExists() { + if (ref.current) { + if (loading === `eager` && ref.current.complete) { + storeImageloaded(cacheKey) + toggleLoaded(true) + } else { + toggleIsLoading(true) + } + } else { + interval = rAF(toggleIfRefExists) + } + } + toggleIfRefExists() + + return () => { + cRAF(interval) + } + }, []) + + return { + isLoading, + isLoaded, + toggleLoaded, + } +} + +// return () => { +// if (root.current) { +// render(null, root.current); +// } +// }; +// } + +// export function useGatsbyImage({ +// placeholder, +// images, +// width, +// height, +// aspectRatio, +// maxWidth, +// maxHeight, +// loading = 'lazy', +// }: any): any { +// const cacheKey = JSON.stringify(images); +// const ref = useRef(); +// const { isLoading, isLoaded, toggleLoaded } = useImageLoaded( +// cacheKey, +// loading, +// ref +// ); + +// return { +// getWrapperProps: () => +// getWrapperProps(width, height, layout), +// getMainImageProps: () => +// getMainProps( +// isLoading || hasNativeLazyLoadSupport, +// isLoaded, +// images, +// loading, +// aspectRatio, +// toggleLoaded, +// cacheKey, +// ref +// ), +// getPlaceholderProps: () => getPlaceHolderProps(placeholder), +// }; +// } diff --git a/packages/gatsby-plugin-image/src/components/intersection-observer.ts b/packages/gatsby-plugin-image/src/components/intersection-observer.ts new file mode 100644 index 0000000000000..d561d90aa3ccf --- /dev/null +++ b/packages/gatsby-plugin-image/src/components/intersection-observer.ts @@ -0,0 +1,39 @@ +import { RefObject } from "react" + +let intersectionObserver: IntersectionObserver + +export function createIntersectionObserver(callback: Function): any { + // if we don't support intersectionObserver we don't lazy load (Sorry IE 11). + if (!(`IntersectionObserver` in window)) { + return function observe() { + callback() + return function unobserve() {} + } + } + + if (!intersectionObserver) { + intersectionObserver = new IntersectionObserver( + entries => { + entries.forEach(entry => { + if (entry.isIntersecting) { + callback() + } + }) + }, + { + // TODO tweak + rootMargin: `150%`, + } + ) + } + + return function observe(element: RefObject) { + intersectionObserver.observe(element.current) + + return function unobserve() { + if (intersectionObserver && element.current) { + intersectionObserver.unobserve(element.current) + } + } + } +} diff --git a/packages/gatsby-plugin-image/src/components/layout-wrapper.tsx b/packages/gatsby-plugin-image/src/components/layout-wrapper.tsx new file mode 100644 index 0000000000000..11d130fac0cb2 --- /dev/null +++ b/packages/gatsby-plugin-image/src/components/layout-wrapper.tsx @@ -0,0 +1,79 @@ +/* global SERVER */ +import React, { + createElement, + Fragment, + FunctionComponent, + ReactNode, +} from "react" + +const terserMacro = require(`../macros/terser.macro`) + +export interface LayoutWrapperProps { + layout: "intrinsic" | "responsive" | "fixed" + width: number + height: number +} + +const NativeScriptLoading = () => ( +