Skip to content

Commit 5eb93b3

Browse files
committed
chore: wip
1 parent 5be65a1 commit 5eb93b3

File tree

6 files changed

+93
-52
lines changed

6 files changed

+93
-52
lines changed

packages/imgx/src/plugins.ts

+43-38
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { BunPlugin, OnLoadResult } from 'bun'
22
import type { Plugin } from 'vite'
33
import type { OptimizeResult, ProcessOptions } from './types'
4-
import { Buffer } from 'node:buffer'
54
import process from 'node:process'
65
import { process as processImage } from './core'
76
import { debugLog } from './utils'
@@ -17,7 +16,7 @@ export function viteImgxPlugin(options: ImgxPluginOptions = {}): Plugin {
1716
const {
1817
include = ['**/*.{jpg,jpeg,png,webp,avif,svg}'],
1918
exclude = ['node_modules/**'],
20-
disabled = process.env?.NODE_ENV === 'development',
19+
disabled = typeof process !== 'undefined' && process.env?.NODE_ENV === 'development',
2120
...processOptions
2221
} = options
2322

@@ -31,35 +30,38 @@ export function viteImgxPlugin(options: ImgxPluginOptions = {}): Plugin {
3130
return {
3231
name: 'vite-plugin-imgx',
3332
apply: 'build',
34-
async transform(code, id) {
35-
if (!id.match(/\.(jpg|jpeg|png|webp|avif|svg)$/i))
36-
return null
37-
38-
// Check include/exclude patterns
39-
const shouldInclude = include.some(pattern => id.match(new RegExp(pattern)))
40-
const shouldExclude = exclude.some(pattern => id.match(new RegExp(pattern)))
41-
42-
if (!shouldInclude || shouldExclude)
33+
async transform(code: string, id: string) {
34+
if (!id.match(/\.(jpe?g|png|webp|avif|svg)$/))
4335
return null
4436

4537
try {
46-
debugLog('vite', `Processing ${id}`)
38+
// Filter files based on include/exclude patterns
39+
const matchesInclude = include.some(pattern => id.match(new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'))))
40+
const matchesExclude = exclude.some(pattern => id.match(new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'))))
41+
42+
if (!matchesInclude || matchesExclude) {
43+
return null
44+
}
4745

4846
const result = await processImage({
4947
...processOptions,
5048
input: id,
5149
})
5250

53-
return {
54-
code: `export default ${JSON.stringify(result.outputPath)}`,
55-
map: null,
51+
debugLog('vite-plugin', `Processed ${id}: saved ${result.saved} bytes (${result.savedPercentage.toFixed(2)}%)`)
52+
53+
if (result.saved > 0) {
54+
return {
55+
code,
56+
map: null,
57+
}
5658
}
5759
}
58-
catch (error: unknown) {
59-
const errorMessage = error instanceof Error ? error.message : String(error)
60-
debugLog('error', `Failed to process ${id}: ${errorMessage}`)
61-
return null
60+
catch (error) {
61+
console.error(`[imgx] Error processing ${id}:`, error)
6262
}
63+
64+
return null
6365
},
6466
}
6567
}
@@ -69,7 +71,7 @@ export function bunImgxPlugin(options: ImgxPluginOptions = {}): BunPlugin {
6971
const {
7072
include = ['**/*.{jpg,jpeg,png,webp,avif,svg}'],
7173
exclude = ['node_modules/**'],
72-
disabled = process.env?.NODE_ENV === 'development',
74+
disabled = false,
7375
...processOptions
7476
} = options
7577

@@ -83,32 +85,35 @@ export function bunImgxPlugin(options: ImgxPluginOptions = {}): BunPlugin {
8385
return {
8486
name: 'bun-plugin-imgx',
8587
setup(build) {
86-
build.onLoad({ filter: /\.(jpg|jpeg|png|webp|avif|svg)$/i }, async (args) => {
87-
const id = args.path
88-
89-
// Check include/exclude patterns
90-
const shouldInclude = include.some(pattern => id.match(new RegExp(pattern)))
91-
const shouldExclude = exclude.some(pattern => id.match(new RegExp(pattern)))
92-
93-
if (!shouldInclude || shouldExclude)
94-
return null as unknown as OnLoadResult
95-
88+
build.onLoad({ filter: /\.(jpe?g|png|webp|avif|svg)$/ }, async (args) => {
9689
try {
97-
debugLog('bun', `Processing ${id}`)
90+
// Filter files based on include/exclude patterns
91+
const matchesInclude = include.some(pattern =>
92+
args.path.match(new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'))),
93+
)
94+
const matchesExclude = exclude.some(pattern =>
95+
args.path.match(new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'))),
96+
)
97+
98+
if (!matchesInclude || matchesExclude) {
99+
return null as unknown as OnLoadResult
100+
}
98101

99102
const result = await processImage({
100103
...processOptions,
101-
input: id,
104+
input: args.path,
102105
})
103106

107+
debugLog('bun-plugin', `Processed ${args.path}: saved ${result.saved} bytes (${result.savedPercentage.toFixed(2)}%)`)
108+
109+
// Return the file contents as is - optimization happens in place
104110
return {
105-
contents: `export default ${JSON.stringify(result.outputPath)}`,
106-
loader: 'js',
107-
}
111+
contents: await Bun.file(args.path).arrayBuffer(),
112+
loader: 'file',
113+
} as OnLoadResult
108114
}
109-
catch (error: unknown) {
110-
const errorMessage = error instanceof Error ? error.message : String(error)
111-
debugLog('error', `Failed to process ${id}: ${errorMessage}`)
115+
catch (error) {
116+
console.error(`[imgx] Error processing ${args.path}:`, error)
112117
return null as unknown as OnLoadResult
113118
}
114119
})

packages/imgx/src/responsive.ts

+34-11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import type { ProcessOptions } from './types'
2+
import { readFile } from 'node:fs/promises'
3+
import { join } from 'node:path'
4+
import sharp from 'sharp'
5+
import { debugLog } from './utils'
6+
17
interface ResponsiveImageOptions extends ProcessOptions {
28
breakpoints: number[]
39
formats: ('webp' | 'avif' | 'jpeg' | 'png')[]
@@ -14,7 +20,7 @@ interface ResponsiveImageResult {
1420
format: string
1521
size: number
1622
}>
17-
srcset: Record<string, string>
23+
srcset: Record<string, string[]>
1824
htmlMarkup: string
1925
}
2026

@@ -31,14 +37,24 @@ export async function generateResponsiveImages(
3137
generateSrcset = true,
3238
} = options
3339

40+
if (typeof input !== 'string') {
41+
throw new TypeError('Input must be a string path')
42+
}
43+
3444
debugLog('responsive', `Generating responsive images for ${input}`)
3545

3646
const inputBuffer = await readFile(input)
3747
const metadata = await sharp(inputBuffer).metadata()
38-
const originalWidth = metadata.width
48+
const originalWidth = metadata.width || 0
3949

40-
const variants = []
41-
const srcset: Record<string, string> = {}
50+
const variants: Array<{
51+
path: string
52+
width: number
53+
format: string
54+
size: number
55+
}> = []
56+
57+
const srcset: Record<string, string[]> = {}
4258

4359
for (const format of formats) {
4460
srcset[format] = []
@@ -47,8 +63,9 @@ export async function generateResponsiveImages(
4763
if (width > originalWidth)
4864
continue
4965

66+
const inputName = input.split('/').pop()?.split('.')[0] || 'image'
5067
const filename = filenameTemplate
51-
.replace('[name]', input.split('/').pop().split('.')[0])
68+
.replace('[name]', inputName)
5269
.replace('[width]', width.toString())
5370
.replace('[ext]', format)
5471

@@ -64,7 +81,7 @@ export async function generateResponsiveImages(
6481
path: outputPath,
6582
width,
6683
format,
67-
size,
84+
size: size || 0,
6885
})
6986

7087
if (generateSrcset) {
@@ -83,12 +100,12 @@ export async function generateResponsiveImages(
83100
sizes="(max-width: ${Math.max(...breakpoints)}px) 100vw, ${Math.max(...breakpoints)}px"
84101
/>`).join('\n')}
85102
<img
86-
src="${variants[0].path}"
103+
src="${variants[0]?.path || ''}"
87104
alt=""
88105
loading="lazy"
89106
decoding="async"
90-
width="${variants[0].width}"
91-
height="${Math.round(variants[0].width * (metadata.height / metadata.width))}"
107+
width="${variants[0]?.width || 0}"
108+
height="${Math.round((variants[0]?.width || 0) * ((metadata.height || 0) / (metadata.width || 1)))}"
92109
/>
93110
</picture>`.trim()
94111

@@ -109,12 +126,18 @@ interface ImageSetOptions {
109126
quality?: number
110127
}
111128

112-
export async function generateImageSet(options: ImageSetOptions) {
129+
export async function generateImageSet(options: ImageSetOptions): Promise<Array<{
130+
size: { width: number, height?: number, suffix?: string }
131+
path: string
132+
}>> {
113133
const { input, name, sizes, outputDir, format = 'png', quality = 80 } = options
114134

115135
debugLog('imageset', `Generating image set for ${input}`)
116136

117-
const results = []
137+
const results: Array<{
138+
size: { width: number, height?: number, suffix?: string }
139+
path: string
140+
}> = []
118141

119142
for (const size of sizes) {
120143
const suffix = size.suffix || `${size.width}x${size.height || size.width}`
Loading
Loading

packages/imgx/test/output/test1.jpg

+1
Loading

packages/imgx/test/plugins.test.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { describe, expect, it } from 'bun:test'
22
import { bunImgxPlugin, viteImgxPlugin } from '../src/plugins'
33

4-
// Mock the process object to avoid process.env.NODE_ENV error
5-
globalThis.process = globalThis.process || { env: {} }
6-
74
describe('plugins', () => {
85
it('should export viteImgxPlugin function', () => {
96
expect(viteImgxPlugin).toBeDefined()
@@ -21,9 +18,22 @@ describe('plugins', () => {
2118
expect(plugin.apply).toBe('build')
2219
})
2320

21+
it('should return a disabled Vite plugin when disabled option is true', () => {
22+
const plugin = viteImgxPlugin({ disabled: true })
23+
expect(plugin.name).toBe('vite-plugin-imgx')
24+
expect(plugin.apply).toBe('build')
25+
expect(plugin.transform).toBeUndefined()
26+
})
27+
2428
it('should return a valid Bun plugin object', () => {
2529
const plugin = bunImgxPlugin()
2630
expect(plugin.name).toBe('bun-plugin-imgx')
2731
expect(typeof plugin.setup).toBe('function')
2832
})
33+
34+
it('should return a disabled Bun plugin when disabled option is true', () => {
35+
const plugin = bunImgxPlugin({ disabled: true })
36+
expect(plugin.name).toBe('bun-plugin-imgx')
37+
expect(typeof plugin.setup).toBe('function')
38+
})
2939
})

0 commit comments

Comments
 (0)