Skip to content

Commit 7efc251

Browse files
committed
feat: 🚀 Support next/future/image
✅Closes: #255
1 parent b1f787e commit 7efc251

File tree

9 files changed

+170
-15
lines changed

9 files changed

+170
-15
lines changed

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This makes it possible to build a high performance website with this solution, w
2323
- Using `sharp`, so it's fast.
2424
- Cache prevents repeating the same optimization
2525
- Support TypeScript
26+
- Support `next/future/image`
2627

2728
## Installation
2829

4.75 MB
Loading

‎__tests__/e2e/index.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import fs from 'fs-extra'
55
const exist = (filename: string) => fs.existsSync(path.resolve(__dirname, 'out/_next/static/chunks/images', filename))
66

77
const files = [
8+
// next/image
89
'_next/static/media/img.8a5ad2fe_1920_75.webp',
910
'_next/static/media/img.8a5ad2fe_3840_75.webp',
1011
'images/img_640_75.webp',
@@ -19,6 +20,17 @@ const files = [
1920
'images/img_3840_75.svg',
2021
'og_1920_75.webp',
2122
'og_3840_75.webp',
23+
// next/future/image
24+
'_next/static/media/future-img.8a5ad2fe_1920_75.webp',
25+
'_next/static/media/future-img.8a5ad2fe_3840_75.webp',
26+
'images/future-img_640_75.webp',
27+
'images/future-img_750_75.webp',
28+
'images/future-img_828_75.webp',
29+
'images/future-img_1080_75.webp',
30+
'images/future-img_1200_75.webp',
31+
'images/future-img_1920_75.webp',
32+
'images/future-img_2048_75.webp',
33+
'images/future-img_3840_75.webp',
2234
]
2335

2436
describe('`next build && next export && next-export-optimize-images` is executed correctly', () => {

‎__tests__/e2e/pages/index.jsx

+28-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React, { useEffect, useState } from 'react'
22

3+
import FutureImage from '../../../dist/future-image'
34
import Image from '../../../dist/image'
45
import clientOnlySrc from '../images/client-only.png'
6+
import futureImgSrc from '../images/future-img.png'
57
import imgSrc from '../images/img.png'
68

79
const IndexPage = () => {
@@ -11,21 +13,32 @@ const IndexPage = () => {
1113
}, [])
1214

1315
return (
14-
<div>
15-
{/* Imported image */}
16-
<Image src={imgSrc} />
17-
18-
{/* Static image */}
19-
<Image src="/images/img.png" width={1920} height={1280} sizes="(min-width: 768px) 720px, 85vw" />
20-
21-
{/* Invalid format image */}
22-
<Image src="/images/img.svg" width={1920} height={1280} />
23-
24-
{/* External image */}
25-
<Image src="https://next-export-optimize-images.vercel.app/og.png" width={1920} height={1280} />
26-
27-
{isClient && <Image src={clientOnlySrc} />}
28-
</div>
16+
<>
17+
{/* next/image */}
18+
<div>
19+
{/* Imported image */}
20+
<Image src={imgSrc} />
21+
22+
{/* Static image */}
23+
<Image src="/images/img.png" width={1920} height={1280} sizes="(min-width: 768px) 720px, 85vw" />
24+
25+
{/* Invalid format image */}
26+
<Image src="/images/img.svg" width={1920} height={1280} />
27+
28+
{/* External image */}
29+
<Image src="https://next-export-optimize-images.vercel.app/og.png" width={1920} height={1280} />
30+
31+
{isClient && <Image src={clientOnlySrc} />}
32+
</div>
33+
{/* next/future/image */}
34+
<div>
35+
{/* Imported image */}
36+
<FutureImage src={futureImgSrc} />
37+
38+
{/* Static image */}
39+
<FutureImage src="/images/future-img.png" width={1920} height={1280} sizes="(min-width: 768px) 720px, 85vw" />
40+
</div>
41+
</>
2942
)
3043
}
3144

4.75 MB
Loading

‎docs/docs/01-intro.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ This makes it possible to build a high performance website with this solution, w
2424
- Using `sharp`, so it's fast.
2525
- Cache prevents repeating the same optimization
2626
- Support TypeScript
27+
- Support `next/future/image`

‎docs/docs/02-getting-started.md

+7
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ module.exports = withPlugins(
5858
<Image src={require('./img.png')} alt="" />
5959
```
6060

61+
Alternatively, you can use `next/future/image`.
62+
63+
```jsx
64+
import Image from 'next/future/image'
65+
;<Image src="/images/img.png" width={1920} height={1280} alt="" />
66+
```
67+
6168
## Local checks
6269

6370
1. Run `yarn export`.

‎src/future-image.tsx

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/* eslint-disable @typescript-eslint/no-var-requires */
2+
import Image, { ImageLoader, ImageProps } from 'next/dist/client/future/image'
3+
import React from 'react'
4+
5+
import type { Manifest } from './cli/types'
6+
import formatValidate from './cli/utils/formatValidate'
7+
import getConfig, { ParsedImageInfo } from './utils/getConfig'
8+
9+
const config = getConfig()
10+
11+
const defaultImageParser: (src: string) => ParsedImageInfo = (src: string) => {
12+
const path = src.split(/\.([^.]*$)/)[0]
13+
const extension = src.split(/\.([^.]*$)/)[1]
14+
15+
if (!path || !extension) {
16+
throw new Error(`Invalid path or no file extension: ${src}`)
17+
}
18+
19+
let pathWithoutName = path.split('/').slice(0, -1).join('/')
20+
const name = path.split('/').slice(-1).toString()
21+
22+
if (src.startsWith('http')) {
23+
pathWithoutName = pathWithoutName
24+
.replace(/^https?:\/\//, '')
25+
.split('/')
26+
.slice(1)
27+
.join('/')
28+
}
29+
30+
return {
31+
pathWithoutName,
32+
name,
33+
extension,
34+
}
35+
}
36+
37+
const exportableLoader: ImageLoader = ({ src: _src, width, quality }) => {
38+
if (process.env.NODE_ENV === 'development') {
39+
// This doesn't bother optimizing in the dev environment. Next complains if the
40+
// returned URL doesn't have a width in it, so adding it as a throwaway
41+
return `${_src}?width=${width}`
42+
}
43+
44+
let src = _src
45+
46+
if (config.basePath !== undefined) {
47+
src = _src.replace(config.basePath, '')
48+
}
49+
50+
const parsedImageInformation = config.sourceImageParser
51+
? config.sourceImageParser({ src, defaultParser: defaultImageParser })
52+
: defaultImageParser(src)
53+
54+
let { extension } = parsedImageInformation
55+
const { pathWithoutName, name } = parsedImageInformation
56+
57+
if (config.convertFormat !== undefined) {
58+
const convertArray = config.convertFormat.find(([beforeConvert]) => beforeConvert === extension)
59+
if (convertArray !== undefined) {
60+
if (!formatValidate(convertArray[0]))
61+
throw Error(`Unauthorized format specified in \`configFormat\`. beforeConvert: ${convertArray[0]}`)
62+
if (!formatValidate(convertArray[1]))
63+
throw Error(`Unauthorized format specified in \`configFormat\`. afterConvert: ${convertArray[1]}`)
64+
65+
extension = convertArray[1]
66+
}
67+
}
68+
69+
const outputDir = `/${
70+
config.imageDir ? config.imageDir.replace(/^\//, '').replace(/\/$/, '') : '_next/static/chunks/images'
71+
}`
72+
const externalOutputDir = `${
73+
config.externalImageDir ? config.externalImageDir.replace(/^\//, '').replace(/\/$/, '') : '_next/static/media'
74+
}`
75+
const filename =
76+
config.filenameGenerator !== undefined
77+
? config.filenameGenerator({ path: pathWithoutName, name, width, quality: quality || 75, extension })
78+
: `${pathWithoutName}/${name}_${width}_${quality || 75}.${extension}`
79+
const output = `${outputDir}/${filename.replace(/^\//, '')}`
80+
81+
if (typeof window === 'undefined' || process.env['TEST_JSON_PATH'] !== undefined) {
82+
const json: Manifest[number] = { output, src, width, quality: quality || 75, extension }
83+
const fs = require('fs-extra') as typeof import('fs-extra')
84+
const path = require('path') as typeof import('path')
85+
86+
if (src.startsWith('http')) {
87+
json.src = `/${externalOutputDir}/${src
88+
.replace(/^https?:\/\//, '')
89+
.split('/')
90+
.slice(1)
91+
.join('/')}`
92+
json.externalUrl = src
93+
}
94+
95+
fs.appendFileSync(
96+
path.join(process.cwd(), process.env['TEST_JSON_PATH'] ?? '.next/custom-optimized-images.nd.json'),
97+
JSON.stringify(json) + '\n'
98+
)
99+
}
100+
101+
return `${config.basePath ?? ''}${output}`
102+
}
103+
104+
const CustomImage = (props: ImageProps) => {
105+
return (
106+
<Image
107+
{...props}
108+
loader={props.loader || exportableLoader}
109+
blurDataURL={
110+
props.blurDataURL ||
111+
(typeof props.src === 'string' && props.placeholder === 'blur' && props.loader === undefined
112+
? exportableLoader({ src: props.src, width: 8, quality: 10 })
113+
: '')
114+
}
115+
/>
116+
)
117+
}
118+
119+
export * from 'next/dist/client/future/image'
120+
export default CustomImage

‎src/withExportImages.ts

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const withExportImages = (nextConfig: NextConfig = {}, options?: Options): NextC
4343
]
4444
} else {
4545
config.resolve.alias['next/image'] = 'next-export-optimize-images/dist/image'
46+
config.resolve.alias['next/future/image'] = 'next-export-optimize-images/dist/future-image'
4647
delete config.resolve.alias['next']
4748
}
4849

0 commit comments

Comments
 (0)