-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Create sub-sized images #74566
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Create sub-sized images #74566
Conversation
Test images here: https://github.com/WordPress/wordpress-develop/tree/trunk/tests/phpunit/data/images The best way to test autorotation is to try turning it off to see how that impacts uploads:
I am double checking that this works as expected with client side media. |
This didn't work as I expected even with client side media disabled. I will loop back to confirming this later, lets focus on the other functionality for now. |
Thanks @andrewserong - enabled in 4a4b9ca |
I missed this earlier, these should be good for testing. testing uploads in the media library should give the expected (baseline) behavior we want to match. |
Limit concurrent VIPS/WASM image processing operations to prevent out-of-memory crashes when uploading many images at once. Each operation can consume 50-100MB+ of memory for large images. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <[email protected]> Co-Authored-By: Happy <[email protected]>
Part of the image processing concurrency limiting work to prevent OOM crashes during bulk image uploads.
Sets the default maxConcurrentImageProcessing value in the upload-media store's DEFAULT_STATE so the concurrency limit is active out of the box.
These selectors mirror the existing upload concurrency selectors and will be used to enforce limits on concurrent VIPS/WASM image processing operations.
Without this gate, all images start VIPS/WASM processing simultaneously when uploaded in bulk, consuming 50-100MB+ each and crashing the browser. Now at most 2 run at once.
Without this, items gated by the concurrency limit would never be retried. Mirrors existing pattern where finished uploads trigger pending upload items.
After bulk image processing, the WASM worker can hold hundreds of MB. Terminating it when the queue is empty frees that memory. The worker is lazily re-created on next use.
Covers getActiveImageProcessingCount and getPendingImageProcessing, verifying they correctly identify ResizeCrop and Rotate operations.
I was able to reproduce this readily by dragging more than a few images into the editor. The code was trying to process them all at once which doesn't make sense. I reduced this to 2 concurrent operations so the next operation starts as the last completes (5cc1bda, 46f4640, 3a43770, 86ece0c). This commit makes sure vips gets cleaned up after use: ed2b000 After these changes I was no longer able to reproduce the out of memory error. |
The Storybook vips stub and Jest test mock were not updated when terminateVipsWorker was introduced, causing CI failures.
Created: #75320 I also noted this bug in the testing instructions |
When client-side media processing is enabled, the spinner and dimmed overlay now persist while sub-sized images are being generated and uploaded, preventing users from closing the window or publishing with missing image sizes.
@andrewserong addressed this in ccd49b3- |
|
@andrewserong & @ramonjd - I addresses all your feedback and this is ready for another review. Let me know if you have any questions or if I missed anything. I would love to land this soon so I can rebase the remaining PRs which nearly all rely on the changes in this PR. We can address any remaining bugs you find in a separate issue. Things to note
Smaller PRs depending on this one
|
| console.warn( | ||
| 'Failed to rotate image, continuing with thumbnails' | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker, just curious about whether we should one day capture error details for user feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, especially if there is some action users can. take to fix the issue. I am working on progress reporting and error handling in follow up PRs.
|
This is definitely testing better for me (I've only done smoke testing so far, but the issues I found the other day appear to be resolved)! Overall I'm supportive of merging sooner than later and iterating as we can. The main blocker I can see is that while this is guarded behind an experiment, it adds an enormous amount to the JS packages according to the automated comment on the PR: #74566 (comment) (3.81MB to the block-editor minified JS, and 3.82MB to the block-library minified JS). Unfortunately, I think we'll need to address this before merge, or it'll affect the size of these bundles even when the experiment isn't active. Is this because the |
| /** | ||
| * External dependencies | ||
| */ | ||
| import { | ||
| vipsConvertImageFormat as convertImageFormat, | ||
| vipsCompressImage as compressImage, | ||
| vipsHasTransparency as hasTransparency, | ||
| vipsResizeImage as resizeImage, | ||
| vipsRotateImage as rotateImage, | ||
| vipsCancelOperations as cancelOperations, | ||
| terminateVipsWorker, | ||
| } from '@wordpress/vips/worker'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please take this suggestion with a necessary grain of salt as I know next to nothing about optimising loading for the WASM, but when I asked Claude about the large bundle sizes, it flagged these lines as static imports that suddenly add to the bundle size.
Not sure if or how possible it might be, but is there a way to lazy load @wordpress/vips/worker when needed during a function call, so that simply importing upload-media doesn't add it all at once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure, but worth investigating further.
Thanks for the continued testing and reviews.
This is missing, removing from the testing instructions!
Excellent, thanks for testing!
yes exactly, it wasn't working in my testing (even on the server), but I must have been doing something wrong. I will work on fixing this, in the br or in a follow up bug issue. |
Excellent... same!
Yes, lets give that a try! I'll get that merged first then rebase this branch. |
|
I merged #74951 into trunk and then trunk into this branch. In my local build this seems to have fixed the bundle size. I assume the CI will run and update the PR warning. Claude also suggested lazy loading @wordpress/vips which I am investigating now. Makes sense to only load vips when we need it. |




What?
Closes #74355
Depends on #74352
Add client-side thumbnail generation for uploaded images in the
@wordpress/upload-mediapackage. When an image is uploaded, this PR enables the browser to generate sub-sized images (thumbnails) using the vips library in a web worker, then sideloads them to the server.Why?
By generating thumbnails client-side using WASM (via the
@wordpress/vipspackage), we can:How?
This PR introduces several key changes to the
@wordpress/upload-mediapackage:Vips utility wrappers (
packages/upload-media/src/store/utils/vips.ts):Thin wrappers around
@wordpress/vips/workerfunctions that handle File/Blob conversions:vipsResizeImage: Resizes images to specified dimensions with optional cropping and dimension suffixvipsConvertImageFormat: Converts images between formats (JPEG, PNG, WebP, AVIF, GIF)vipsCompressImage: Compresses images with quality controlvipsHasTransparency: Checks if an image has transparency (with error handling fallback)vipsRotateImage: Rotates images based on EXIF orientation valuesvipsCancelOperations: Cancels ongoing vips operations for a given queue itemNew operation types (
OperationTypeenum):ResizeCrop: Resizes and crops images to specific dimensions viaresizeCropItemactionThumbnailGeneration: Orchestrates creating multiple thumbnail sizes viagenerateThumbnailsactionRotate: Handles EXIF-based image rotation viarotateItemactionBig image size threshold scaling:
bigImageSizeThresholdsetting from REST API (default 2560px) through block editor settings-scaledsuffix to scaled images, matching WordPress core'swp_create_image_subsizes()behaviorAutomatic image rotation:
exif_orientationfrom REST API whengenerate_sub_sizes=false-rotatedversion sideloadedSideloading support:
addSideloadItemaction: Queues a file for sideloading (typically a generated thumbnail)sideloadItemaction: Uploads generated thumbnails to existing attachments viamediaSideloadSideloadMediaArgsinterface: Defines the contract for sideload operationsSideloadAdditionalDatainterface: Includespost(attachment ID) andimage_sizenameImage size configuration:
allImageSizesto Settings type: Maps size names to{ width, height, crop }definitionsQueue management improvements:
isUploadingByParentIdselector: Tracks child uploads to prevent premature parent removalisUploadingToPostselector: Detects concurrent uploads to the same attachmentgetPausedUploadForPostselector: Finds paused items for a given post/attachmentshouldPauseForSideloadhelper: Pauses uploads to avoid race conditions when sideloadingresumeItemByPostIdaction: Resumes paused uploads after sideload completesmissing_image_sizesfrom attachment response to determine which sizes need generationTesting Instructions
Prerequisites / Setup
Enable the Client Side Media experiment
Prepare test images (for comprehensive testing):
Open browser DevTools → Network tab to observe upload behavior
Feature 1: Basic Client-Side Thumbnail Generation
What it does: When an image is uploaded, the browser generates sub-sized images (thumbnails) using the vips library, then sideloads them to the server.
Testing steps:
/wp/v2/media)image_sizeparameter)Feature 2: Big Image Size Threshold Scaling
What it does: Images larger than 2560px (configurable via
big_image_size_thresholdfilter) are automatically scaled down before upload, with a-scaledsuffix added. Note one bug: currently the original image is not uploaded when a scaled image is created, see #75320.Testing steps:
-scaledin its filename/wp-json/wp/v2/media/{id})big_image_size_thresholdfilter to adjust the threshold and test againEdge cases to test:
Feature 3: Automatic Image Rotation (EXIF)
What it does: Images with EXIF orientation data are automatically rotated to display correctly, matching WordPress core's server-side behavior.
Testing steps:
For images smaller than threshold that need rotation:
-rotatedsuffix version is created and sideloadedEdge case:
Feature 4: Upload Cancellation
What it does: Canceling an upload mid-process properly cleans up all related operations including thumbnail generation.
Testing steps:
Feature 5: Custom Image Sizes (Theme/Plugin)
What it does: Custom image sizes registered by themes or plugins are also generated client-side.
Testing steps:
functions.php:Feature 6: Concurrent Upload Handling
What it does: Multiple images can be uploaded simultaneously without conflicts.
Testing steps:
Error Handling (Optional)
Network failure during sideload:
Notes for Reviewers
POSTrequests per image uploadTechnical Notes
Dependencies added:
@wordpress/vips: WebAssembly-based image processing library- see Load vips library with new wordpress/worker-threads #74785New/Updated Types:
ImageSizeCrop: Defines width, height, and crop settings for image sizes (supports positional crop anchors)SideloadAdditionalData: Additional data for sideload requests (attachment ID, image size name)SideloadMediaArgs: Arguments for themediaSideloadfunctionSettings.allImageSizes: Registry of available image sizes from the serverSettings.bigImageSizeThreshold: Threshold for automatic image scaling (default 2560)Test coverage:
isUploadingByParentIdselector