@@ -22,7 +22,7 @@ use turbo_tasks_fs::{File, FileContent, FileSystemPath};
2222use turbopack_core:: {
2323 error:: PrettyPrintError ,
2424 ident:: AssetIdent ,
25- issue:: { Issue , IssueExt } ,
25+ issue:: { Issue , IssueExt , IssueSeverity } ,
2626} ;
2727
2828use self :: svg:: calculate;
@@ -108,6 +108,8 @@ fn result_to_issue<T>(ident: Vc<AssetIdent>, result: Result<T>) -> Option<T> {
108108 ImageProcessingIssue {
109109 path : ident. path ( ) ,
110110 message : Vc :: cell ( format ! ( "{}" , PrettyPrintError ( & err) ) ) ,
111+ issue_severity : None ,
112+ title : None ,
111113 }
112114 . cell ( )
113115 . emit ( ) ;
@@ -120,14 +122,22 @@ fn load_image(
120122 ident : Vc < AssetIdent > ,
121123 bytes : & [ u8 ] ,
122124 extension : Option < & str > ,
123- ) -> Option < ( image:: DynamicImage , Option < ImageFormat > ) > {
124- result_to_issue ( ident, load_image_internal ( bytes, extension) )
125+ ) -> Option < ( ImageBuffer , Option < ImageFormat > ) > {
126+ result_to_issue ( ident, load_image_internal ( ident, bytes, extension) )
127+ }
128+
129+ /// Type of raw image buffer read by reader from `load_image`.
130+ /// If the image could not be decoded, the raw bytes are returned.
131+ enum ImageBuffer {
132+ Raw ( Vec < u8 > ) ,
133+ Decoded ( image:: DynamicImage ) ,
125134}
126135
127136fn load_image_internal (
137+ ident : Vc < AssetIdent > ,
128138 bytes : & [ u8 ] ,
129139 extension : Option < & str > ,
130- ) -> Result < ( image :: DynamicImage , Option < ImageFormat > ) > {
140+ ) -> Result < ( ImageBuffer , Option < ImageFormat > ) > {
131141 let reader = image:: io:: Reader :: new ( Cursor :: new ( & bytes) ) ;
132142 let mut reader = reader
133143 . with_guessed_format ( )
@@ -141,8 +151,51 @@ fn load_image_internal(
141151 }
142152 }
143153 }
154+
155+ // [NOTE]
156+ // Workaround for missing codec supports in Turbopack,
157+ // Instead of erroring out the whole build, emitting raw image bytes as-is
158+ // (Not applying resize, not applying optimization or anything else)
159+ // and expect a browser decodes it.
160+ // This is a stop gap until we have proper encoding/decoding in majority of the
161+ // platforms
162+
163+ #[ cfg( not( feature = "avif" ) ) ]
164+ if matches ! ( format, Some ( ImageFormat :: Avif ) ) {
165+ ImageProcessingIssue {
166+ path : ident. path ( ) ,
167+ message : Vc :: cell (
168+ "This version of Turbopack does not support AVIF images, will emit without \
169+ optimization or encoding"
170+ . to_string ( ) ,
171+ ) ,
172+ title : Some ( Vc :: cell ( "AVIF image not supported" . to_string ( ) ) ) ,
173+ issue_severity : Some ( IssueSeverity :: Warning . into ( ) ) ,
174+ }
175+ . cell ( )
176+ . emit ( ) ;
177+ return Ok ( ( ImageBuffer :: Raw ( bytes. to_vec ( ) ) , format) ) ;
178+ }
179+
180+ #[ cfg( not( feature = "webp" ) ) ]
181+ if matches ! ( format, Some ( ImageFormat :: WebP ) ) {
182+ ImageProcessingIssue {
183+ path : ident. path ( ) ,
184+ message : Vc :: cell (
185+ "This version of Turbopack does not support WEBP images, will emit without \
186+ optimization or encoding"
187+ . to_string ( ) ,
188+ ) ,
189+ title : Some ( Vc :: cell ( "WEBP image not supported" . to_string ( ) ) ) ,
190+ issue_severity : Some ( IssueSeverity :: Warning . into ( ) ) ,
191+ }
192+ . cell ( )
193+ . emit ( ) ;
194+ return Ok ( ( ImageBuffer :: Raw ( bytes. to_vec ( ) ) , format) ) ;
195+ }
196+
144197 let image = reader. decode ( ) . context ( "unable to decode image data" ) ?;
145- Ok ( ( image, format) )
198+ Ok ( ( ImageBuffer :: Decoded ( image) , format) )
146199}
147200
148201fn compute_blur_data (
@@ -159,6 +212,8 @@ fn compute_blur_data(
159212 ImageProcessingIssue {
160213 path : ident. path ( ) ,
161214 message : Vc :: cell ( format ! ( "{}" , PrettyPrintError ( & err) ) ) ,
215+ issue_severity : None ,
216+ title : None ,
162217 }
163218 . cell ( )
164219 . emit ( ) ;
@@ -321,36 +376,42 @@ pub async fn get_meta_data(
321376 let Some ( ( image, format) ) = load_image ( ident, & bytes, extension) else {
322377 return Ok ( ImageMetaData :: fallback_value ( None ) . cell ( ) ) ;
323378 } ;
324- let ( width, height) = image. dimensions ( ) ;
325- let blur_placeholder = if let Some ( blur_placeholder) = blur_placeholder {
326- if matches ! (
327- format,
328- // list should match next/client/image.tsx
329- Some ( ImageFormat :: Png )
330- | Some ( ImageFormat :: Jpeg )
331- | Some ( ImageFormat :: WebP )
332- | Some ( ImageFormat :: Avif )
333- ) {
334- compute_blur_data ( ident, image, format. unwrap ( ) , & * blur_placeholder. await ?)
335- } else {
336- None
337- }
338- } else {
339- None
340- } ;
341379
342- Ok ( ImageMetaData {
343- width,
344- height,
345- mime_type : if let Some ( format) = format {
346- image_format_to_mime_type ( format) ?
347- } else {
348- None
349- } ,
350- blur_placeholder,
351- placeholder_for_future_extensions : ( ) ,
380+ match image {
381+ ImageBuffer :: Raw ( ..) => Ok ( ImageMetaData :: fallback_value ( None ) . cell ( ) ) ,
382+ ImageBuffer :: Decoded ( image) => {
383+ let ( width, height) = image. dimensions ( ) ;
384+ let blur_placeholder = if let Some ( blur_placeholder) = blur_placeholder {
385+ if matches ! (
386+ format,
387+ // list should match next/client/image.tsx
388+ Some ( ImageFormat :: Png )
389+ | Some ( ImageFormat :: Jpeg )
390+ | Some ( ImageFormat :: WebP )
391+ | Some ( ImageFormat :: Avif )
392+ ) {
393+ compute_blur_data ( ident, image, format. unwrap ( ) , & * blur_placeholder. await ?)
394+ } else {
395+ None
396+ }
397+ } else {
398+ None
399+ } ;
400+
401+ Ok ( ImageMetaData {
402+ width,
403+ height,
404+ mime_type : if let Some ( format) = format {
405+ image_format_to_mime_type ( format) ?
406+ } else {
407+ None
408+ } ,
409+ blur_placeholder,
410+ placeholder_for_future_extensions : ( ) ,
411+ }
412+ . cell ( ) )
413+ }
352414 }
353- . cell ( ) )
354415}
355416
356417#[ turbo_tasks:: function]
@@ -365,38 +426,73 @@ pub async fn optimize(
365426 return Ok ( FileContent :: NotFound . cell ( ) ) ;
366427 } ;
367428 let bytes = content. content ( ) . to_bytes ( ) ?;
368- let Some ( ( image, mut format) ) = load_image ( ident, & bytes, ident. path ( ) . await ?. extension_ref ( ) )
429+
430+ let Some ( ( image, format) ) = load_image ( ident, & bytes, ident. path ( ) . await ?. extension_ref ( ) )
369431 else {
370432 return Ok ( FileContent :: NotFound . cell ( ) ) ;
371433 } ;
372- let ( width, height) = image. dimensions ( ) ;
373- let image = if width > max_width || height > max_height {
374- image. resize ( max_width, max_height, FilterType :: Lanczos3 )
375- } else {
376- image
377- } ;
378- #[ cfg( not( feature = "avif" ) ) ]
379- if matches ! ( format, Some ( ImageFormat :: Avif ) ) {
380- format = Some ( ImageFormat :: Jpeg ) ;
381- }
382- #[ cfg( not( feature = "webp" ) ) ]
383- if matches ! ( format, Some ( ImageFormat :: WebP ) ) {
384- format = Some ( ImageFormat :: Jpeg ) ;
385- }
386- let format = format. unwrap_or ( ImageFormat :: Jpeg ) ;
387- let ( data, mime_type) = encode_image ( image, format, quality) ?;
434+ match image {
435+ ImageBuffer :: Raw ( buffer) => {
436+ #[ cfg( not( feature = "avif" ) ) ]
437+ if matches ! ( format, Some ( ImageFormat :: Avif ) ) {
438+ return Ok ( FileContent :: Content (
439+ File :: from ( buffer) . with_content_type ( Mime :: from_str ( "image/avif" ) ?) ,
440+ )
441+ . cell ( ) ) ;
442+ }
388443
389- Ok ( FileContent :: Content ( File :: from ( data) . with_content_type ( mime_type) ) . cell ( ) )
444+ #[ cfg( not( feature = "webp" ) ) ]
445+ if matches ! ( format, Some ( ImageFormat :: WebP ) ) {
446+ return Ok ( FileContent :: Content (
447+ File :: from ( buffer) . with_content_type ( Mime :: from_str ( "image/webp" ) ?) ,
448+ )
449+ . cell ( ) ) ;
450+ }
451+
452+ let mime_type = if let Some ( format) = format {
453+ image_format_to_mime_type ( format) ?
454+ } else {
455+ None
456+ } ;
457+
458+ // Falls back to image/jpeg if the format is unknown, thouogh it is not
459+ // technically correct
460+ Ok ( FileContent :: Content (
461+ File :: from ( buffer) . with_content_type ( mime_type. unwrap_or ( mime:: IMAGE_JPEG ) ) ,
462+ )
463+ . cell ( ) )
464+ }
465+ ImageBuffer :: Decoded ( image) => {
466+ let ( width, height) = image. dimensions ( ) ;
467+ let image = if width > max_width || height > max_height {
468+ image. resize ( max_width, max_height, FilterType :: Lanczos3 )
469+ } else {
470+ image
471+ } ;
472+
473+ let format = format. unwrap_or ( ImageFormat :: Jpeg ) ;
474+ let ( data, mime_type) = encode_image ( image, format, quality) ?;
475+
476+ Ok ( FileContent :: Content ( File :: from ( data) . with_content_type ( mime_type) ) . cell ( ) )
477+ }
478+ }
390479}
391480
392481#[ turbo_tasks:: value]
393482struct ImageProcessingIssue {
394483 path : Vc < FileSystemPath > ,
395484 message : Vc < String > ,
485+ title : Option < Vc < String > > ,
486+ issue_severity : Option < Vc < IssueSeverity > > ,
396487}
397488
398489#[ turbo_tasks:: value_impl]
399490impl Issue for ImageProcessingIssue {
491+ #[ turbo_tasks:: function]
492+ fn severity ( & self ) -> Vc < IssueSeverity > {
493+ self . issue_severity . unwrap_or ( IssueSeverity :: Error . into ( ) )
494+ }
495+
400496 #[ turbo_tasks:: function]
401497 fn file_path ( & self ) -> Vc < FileSystemPath > {
402498 self . path
@@ -407,8 +503,10 @@ impl Issue for ImageProcessingIssue {
407503 }
408504 #[ turbo_tasks:: function]
409505 fn title ( & self ) -> Vc < String > {
410- Vc :: cell ( "Processing image failed" . to_string ( ) )
506+ self . title
507+ . unwrap_or ( Vc :: cell ( "Processing image failed" . to_string ( ) ) )
411508 }
509+
412510 #[ turbo_tasks:: function]
413511 fn description ( & self ) -> Vc < String > {
414512 self . message
0 commit comments