diff --git a/.eslintignore b/.eslintignore index 3b022259..f9c93fc9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -19,4 +19,5 @@ __tests__/e2e picture.js picture.d.ts image.js +image.d.ts legacy diff --git a/__tests__/e2e/app/page.jsx b/__tests__/e2e/app/page.jsx index 99d66950..8338f605 100644 --- a/__tests__/e2e/app/page.jsx +++ b/__tests__/e2e/app/page.jsx @@ -1,5 +1,5 @@ -import Image from 'next/image' -import LegacyImage from 'next/legacy/image' +import Image from '../../../dist/components/image' +import LegacyImage from '../../../dist/components/legacy-image' import React from 'react' import Picture from '../../../dist/components/picture' diff --git a/__tests__/e2e/components/ClientComponent.jsx b/__tests__/e2e/components/ClientComponent.jsx index 285cac81..07f39fe9 100644 --- a/__tests__/e2e/components/ClientComponent.jsx +++ b/__tests__/e2e/components/ClientComponent.jsx @@ -1,7 +1,7 @@ 'use client' import React, { useState, useEffect } from 'react' -import Image from 'next/image' +import Image from '../../../dist/components/image' import clientOnlySrc from '../images/client-only.png' diff --git a/docs/docs/02-getting-started.md b/docs/docs/02-getting-started.md index 7d7d125b..8476bf56 100644 --- a/docs/docs/02-getting-started.md +++ b/docs/docs/02-getting-started.md @@ -25,7 +25,7 @@ module.exports = withExportImages({ }) ``` -1. Change the description of the `scripts` that do the `next build` in `package.json` +2. Change the description of the `scripts` that do the `next build` in `package.json` ```diff title="package.json" { @@ -34,9 +34,11 @@ module.exports = withExportImages({ } ``` -3. Import and use next/image as usual. +3. Import from `next-export-optimize-images/image` and use it. ```tsx +import Image from 'next-export-optimize-images/image' + // Or import as follows import img from './img.png' @@ -48,8 +50,9 @@ import img from './img.png' Alternatively, you can use `next/legacy/image`. ```tsx -import Image from 'next/legacy/image' -; +import Image from 'next-export-optimize-images/legacy/image' + + ``` ## Local checks diff --git a/docs/docs/03-Features/02-picture-component.md b/docs/docs/03-Features/02-picture-component.md index 447cd0f5..b2cde493 100644 --- a/docs/docs/03-Features/02-picture-component.md +++ b/docs/docs/03-Features/02-picture-component.md @@ -1,5 +1,5 @@ --- -description: This page introduces the Picture componen tfor multiple image formats. +description: This page introduces the Picture component for multiple image formats. --- # Picture component diff --git a/docs/docs/06-comparison.md b/docs/docs/05-comparison.md similarity index 100% rename from docs/docs/06-comparison.md rename to docs/docs/05-comparison.md diff --git a/docs/docs/05-structure.md b/docs/docs/05-structure.md deleted file mode 100644 index e7b3a631..00000000 --- a/docs/docs/05-structure.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -description: This page introduces how this library works. ---- - -# Structure - -This page explains how image optimization works. -It will provide you with the following benefits. - -- What you can do with this library will become clearer. -- It is a guidepost when something goes wrong. - -## Processing step - -First, let me explain roughly what kind of processing process is involved. - -1. When you import next/image, change the webpack configuration to automatically load the custom component that wraps next/image from this library. -2. Receive information on images to be optimized through the loader in next/image and write it to a JSON file. -3. After `next export`, optimize the image based on the JSON file you just exported. - -Also, when `next dev`, the loader returns the string almost as is, with the original image, so it does not take any time to build. - -From here, I will explain in more depth. - -## When you import next/image - -```js -import Image from 'next/image' -``` - -Importing a `next/image` component like this will automatically alias it to `next-export-optimzie-images/dist/image`. -This uses the `webpack` alias feature. (https://webpack.js.org/configuration/resolve/#resolvealias) - -## Customize the `next/image` `loader` - -The image component of this library defines an internally customized `loader`. -This is used for the actual rendering `src` and `srcSet` etc. in `next/image`. Also, at build time, the list of images to be optimized is written to a JSON file. - -At this time, a list of images to be optimized is created based on the `layout` attribute, the `sizes` attribute, the `placeholder` attribute, etc. -Therefore, unused images are not created and build time is not needlessly increased. - -For example, suppose you render two image components as follows. - -```jsx -<> - - - -``` - -The images created at this time are as follows - -``` -intrinsic_1280_75.png -intrinsic_2560_75.png -responsive_640_75.png -responsive_750_75.png -responsive_828_75.png -responsive_1080_75.png -responsive_1200_75.png -responsive_1920_75.png -responsive_2048_75.png -responsive_3840_75.png -``` -:::info -Only file names are listed. -Also, if you have set `deviceSizes` etc. in `next.config.js`, it is a little different. -::: - -## Image optimization - -Run `next-export-optimize-images` to start optimizing the images. -This is basically done after `yarn build && yarn export`. - -The image is optimized based on the information in the exported JSON file through the loader described earlier. -At this time, the optimized images are stored once in the `node_modules/.cache` directory, along with cache data (image hash and file path). -The second and subsequent optimizations will use this information to decide whether to skip the optimization or not. - -The mechanism is as follows. - -Search for images with the same file path in the JSON file that stores the cached data. - -- if there is ... → Compare with the hash of that image, and if different, update the hash and create an optimized image. If they are the same, skip optimization. -- if not ... → The hash and file path of that image is stored in cache data to create an optimized image. diff --git a/docs/docs/06-examples.md b/docs/docs/06-examples.md new file mode 100644 index 00000000..837956f7 --- /dev/null +++ b/docs/docs/06-examples.md @@ -0,0 +1,21 @@ +--- +description: This page is to introduce examples of use. +--- + +# Examples + +## Set the `deviceSizes` + +```js title="next.config.js" +module.exports = withExportImages({ + images: { + deviceSizes: [640, 960, 1280, 1600, 1920], + }, +}) +``` + +## Set the `placeholder` + +```jsx + +``` diff --git a/docs/docs/07-examples.md b/docs/docs/07-examples.md deleted file mode 100644 index dab274da..00000000 --- a/docs/docs/07-examples.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -description: This page is to introduce examples of use. ---- - -# Examples - -## Set the `deviceSizes` - -```js title="next.config.js" -module.exports = withExportImages({ - images: { - deviceSizes: [640, 960, 1280, 1600, 1920], - }, -}) -``` - -## Set the `placeholder` - -```jsx - -``` - -## Use next/image separately from image components to be optimized at build time - -```tsx title="CMSImage.tsx" -import Image, { ImageLoaderProps, ImageProps } from 'next/image' -import { FC } from 'react' - -type Props = ImageProps - -const CMSLoader = ({ src, width, quality }: ImageLoaderProps) => { - const url = new URL(normalizeSrc(src)) - const params = url.searchParams - - params.set('auto', params.get('auto') || 'format') - params.set('fit', params.get('fit') || 'max') - params.set('w', params.get('w') || width.toString()) - - if (quality) { - params.set('q', quality.toString()) - } - - return url.href -} - -const CMSImage: FC = (props) => { - return -} - -export default CMSImage -``` diff --git a/docs/docs/08-qa.md b/docs/docs/07-qa.md similarity index 100% rename from docs/docs/08-qa.md rename to docs/docs/07-qa.md diff --git a/docs/docs/09-planned-features.md b/docs/docs/08-planned-features.md similarity index 100% rename from docs/docs/09-planned-features.md rename to docs/docs/08-planned-features.md diff --git a/examples/common/pages/index.jsx b/examples/common/pages/index.jsx index 1fe2e7e3..80de1324 100644 --- a/examples/common/pages/index.jsx +++ b/examples/common/pages/index.jsx @@ -1,4 +1,4 @@ -import Image from 'next/image' +import Image from 'next-export-optimize-images/image' import imgSrc from '../images/img.png' diff --git a/image.d.ts b/image.d.ts new file mode 100644 index 00000000..54c8d48f --- /dev/null +++ b/image.d.ts @@ -0,0 +1,2 @@ +import Image from './dist/components/image' +export default Image diff --git a/legacy/image.d.ts b/legacy/image.d.ts new file mode 100644 index 00000000..4832dfb2 --- /dev/null +++ b/legacy/image.d.ts @@ -0,0 +1,2 @@ +import Image from '../dist/components/legacy-image' +export default Image diff --git a/package.json b/package.json index fa10e418..f0c4949a 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dist", "legacy", "image.js", + "image.d.ts", "picture.js", "picture.d.ts" ], @@ -70,8 +71,8 @@ "@types/fs-extra": "11.0.4", "@types/jest": "29.5.12", "@types/lodash.uniqwith": "4.5.9", - "@types/node": "20.11.28", - "@types/react": "18.2.66", + "@types/node": "20.11.25", + "@types/react": "18.2.64", "@types/recursive-readdir": "^2.2.4", "@types/sharp": "0.31.1", "@typescript-eslint/eslint-plugin": "6.21.0", diff --git a/picture.d.ts b/picture.d.ts index 3b4febc9..90065d06 100644 --- a/picture.d.ts +++ b/picture.d.ts @@ -1,3 +1,2 @@ import Picture from './dist/components/picture' -export * from './dist/components/picture' export default Picture diff --git a/src/components/image.tsx b/src/components/image.tsx index fd068f73..393f3673 100644 --- a/src/components/image.tsx +++ b/src/components/image.tsx @@ -1,18 +1,10 @@ -import Image, { ImageProps, StaticImageData } from 'next/dist/shared/lib/image-external' +import Image, { ImageProps } from 'next/image' import React, { forwardRef } from 'react' import getStringSrc from './utils/getStringSrc' import imageLoader from './utils/imageLoader' -export interface StaticRequire { - default: StaticImageData -} - -interface CustomImageProps extends ImageProps { - src: string | StaticImageData | StaticRequire -} - -const CustomImage = (props: CustomImageProps, forwardedRef: React.ForwardedRef) => { +const CustomImage = forwardRef((props, forwardedRef) => { const srcStr = getStringSrc(props.src) return ( @@ -29,9 +21,7 @@ const CustomImage = (props: CustomImageProps, forwardedRef: React.ForwardedRef ) -} - -const _CustomImage = forwardRef(CustomImage) +}) +CustomImage.displayName = 'CustomImage' -export * from 'next/dist/shared/lib/image-external' -export default _CustomImage +export default CustomImage diff --git a/src/components/legacy-image.tsx b/src/components/legacy-image.tsx index e48ba85c..4cb126e1 100644 --- a/src/components/legacy-image.tsx +++ b/src/components/legacy-image.tsx @@ -1,4 +1,4 @@ -import Image, { ImageProps } from 'next/dist/client/legacy/image' +import Image, { ImageProps } from 'next/image' import React from 'react' import imageLoader from './utils/imageLoader' @@ -18,5 +18,4 @@ const CustomImage = (props: ImageProps) => { ) } -export * from 'next/dist/client/legacy/image' export default CustomImage diff --git a/src/components/picture.tsx b/src/components/picture.tsx index 8f83b4ad..f6e5a7eb 100644 --- a/src/components/picture.tsx +++ b/src/components/picture.tsx @@ -1,4 +1,4 @@ -import Image, { ImageProps, unstable_getImgProps as getImgProps } from 'next/dist/shared/lib/image-external' +import Image, { ImageProps, unstable_getImgProps as getImageProps } from 'next/dist/shared/lib/image-external' import React, { forwardRef } from 'react' import getConfig from '../utils/getConfig' @@ -8,7 +8,7 @@ import imageLoader from './utils/imageLoader' const config = getConfig() -const Picture = (props: ImageProps, forwardedRef: React.ForwardedRef) => { +const Picture = forwardRef((props, forwardedRef) => { const srcStr = getStringSrc(props.src) if (srcStr.endsWith('.svg')) { @@ -17,7 +17,7 @@ const Picture = (props: ImageProps, forwardedRef: React.ForwardedRef ({ - srcSet: getImgProps({ + srcSet: getImageProps({ ...props, loader: imageLoader(i), }).props.srcSet, @@ -42,8 +42,7 @@ const Picture = (props: ImageProps, forwardedRef: React.ForwardedRef ) -} +}) +Picture.displayName = 'Picture' -const _Picture = forwardRef(Picture) - -export default _Picture +export default Picture diff --git a/src/components/utils/getStringSrc.ts b/src/components/utils/getStringSrc.ts index c65400d9..47a1cbcf 100644 --- a/src/components/utils/getStringSrc.ts +++ b/src/components/utils/getStringSrc.ts @@ -1,13 +1,15 @@ import type { ImageProps, StaticImageData } from 'next/dist/shared/lib/image-external' -import type { StaticRequire } from '../image' +type StaticRequire = { + default: StaticImageData +} const getStringSrc = (imgSrc: ImageProps['src']) => { return typeof imgSrc === 'string' ? imgSrc : (imgSrc as StaticRequire).default !== undefined - ? (imgSrc as StaticRequire).default.src - : (imgSrc as StaticImageData).src + ? (imgSrc as StaticRequire).default.src + : (imgSrc as StaticImageData).src } export default getStringSrc diff --git a/src/withExportImages.ts b/src/withExportImages.ts index 2e1d06ce..b837ed04 100644 --- a/src/withExportImages.ts +++ b/src/withExportImages.ts @@ -37,16 +37,6 @@ const withExportImages = (nextConfig: NextConfig = {}, options: { __test?: boole loader: 'custom', }, webpack(config, option) { - config.resolve.alias['next/image'] = options.__test - ? '../../../dist/components/image' - : 'next-export-optimize-images/image' - config.resolve.alias['next/legacy/image'] = options.__test - ? '../../../dist/components/legacy-image' - : 'next-export-optimize-images/legacy/image' - delete config.resolve.alias['next'] - - config.resolve.fallback = { ...config.resolve.fallback, fs: false } - const nextImageLoader = config.module.rules.find( ({ loader }: { loader?: string }) => loader === 'next-image-loader' ) diff --git a/yarn.lock b/yarn.lock index 18bdc6e2..03981f9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1921,10 +1921,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.9.tgz#0b7f161afb5b1cc12518d29b2cdc7175d5490628" integrity sha512-5dNBXu/FOER+EXnyah7rn8xlNrfMOQb/qXnw4NQgLkCygKBKhdmF/CA5oXVOKZLBEahw8s2WP9LxIcN/oDDRgQ== -"@types/node@20.11.28": - version "20.11.28" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.28.tgz#4fd5b2daff2e580c12316e457473d68f15ee6f66" - integrity sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA== +"@types/node@20.11.25": + version "20.11.25" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.25.tgz#0f50d62f274e54dd7a49f7704cc16bfbcccaf49f" + integrity sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw== dependencies: undici-types "~5.26.4" @@ -1964,10 +1964,10 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@18.2.66": - version "18.2.66" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.66.tgz#d2eafc8c4e70939c5432221adb23d32d76bfe451" - integrity sha512-OYTmMI4UigXeFMF/j4uv0lBBEbongSgptPrHBxqME44h9+yNov+oL6Z3ocJKo0WyXR84sQUNeyIp9MRfckvZpg== +"@types/react@18.2.64": + version "18.2.64" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.64.tgz#3700fbb6b2fa60a6868ec1323ae4cbd446a2197d" + integrity sha512-MlmPvHgjj2p3vZaxbQgFUQFvD8QiZwACfGqEdDSWou5yISWxDQ4/74nCAwsUiX7UFLKZz3BbVSPj+YxeoGGCfg== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -8240,16 +8240,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8326,7 +8317,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -8340,13 +8331,6 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -9152,16 +9136,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==