Commit 2453663
Create sub-sized images (#74566)
* feat: add sideloading functionality and image resizing operations to upload media types
* Add getImageSizes and getImageSize selectors
* add imageSizes support to media upload settings and block editor settings
* add dependency for @wordpress/vips in upload-media
* add vips image processing utilities for format conversion, compression, transparency check and resizing
* Add the shopify/web-worker dependency
* export vips utility functions for image processing
* Add thumbnail generation and sideload actions
* linter fixes
* Ensure parentItem exists before using
* Add tests for sideloading and canceling image uploads
* rename imageSizes -> allImageSizes to avoid conflict
* cleanup
* Simplify loading of all image sizes
* update to new vips utils
* update package lock
* refactor vips tests to use updated mock functions
* Add Error Handling in generateThumbnails and hasTransparency
* verify item exists before using
* ensure item available
* Improve docs and add abort signal
* remove async keyword, not needed (?)
* Add bigImageSizeThreshold setting to upload-media store
Pass the big image size threshold (default 2560) from the REST API
through the settings chain to the upload-media store. This enables
client-side scaling of large images before upload, matching WordPress
core's behavior.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Implement big image size threshold scaling
When an image exceeds the big image size threshold (default 2560px),
scale it down before upload with a '-scaled' suffix. This matches
WordPress core's behavior in wp_create_image_subsizes().
- Add isThresholdResize flag to ResizeCrop operation args
- Update vipsResizeImage to support '-scaled' suffix
- Update resizeCropItem to pass through threshold flag
- prepareItem now adds ResizeCrop operation for threshold scaling
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add E2E tests for big image size threshold
Add tests to verify that:
- Images larger than the threshold (default 2560) are scaled down
- The -scaled suffix is added to scaled images
- Images smaller than the threshold are not modified
Tests skip when cross-origin isolation is not enabled, as the vips
library requires SharedArrayBuffer.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Generate thumbnails from original image for better quality
Use sourceFile instead of file when generating thumbnails to match
WordPress core behavior. This ensures thumbnails are created from the
original high-resolution image rather than the scaled version.
Also adds E2E test assertions to verify original_image metadata is
preserved and thumbnails are generated with expected dimensions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Implement automatic image rotation matching server handling
This adds client-side EXIF orientation handling that mirrors WordPress
core's server-side rotation behavior:
- Add exif_orientation field to REST API response for images
- Disable server-side EXIF rotation when generate_sub_sizes=false so
client can handle rotation using the returned orientation value
- Add rotateImage function to vips package supporting all 8 EXIF
orientation values (rotations and flips)
- Add vipsRotateImage wrapper in upload-media store utils
- Add Rotate operation type and rotateItem action handler
- Update generateThumbnails to sideload rotated version for images
that need rotation but weren't scaled (matching -rotated suffix)
- Fix Jest config to resolve @wordpress/vips/worker module for tests
- Add tests for vipsRotateImage function
The implementation ensures:
- Images scaled via bigImageSizeThreshold are auto-rotated by vips
- Small images needing rotation get a -rotated version sideloaded
- Thumbnails are auto-rotated by vips during generation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix build and type issues for image rotation support
- Add type assertion for exif_orientation parameter
- Export vipsRotateImage from utils for external use
- Add ./worker export mapping to vips package
- Mark @wordpress/vips and wasm-vips as always external in build to handle WASM imports
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix CI failures for create subsized images PR
- Change imports from @wordpress/vips/worker to @wordpress/vips
(the /worker subpath is not available in trunk and both export the same module)
- Add missing vips reference to upload-media tsconfig.json
- Remove unnecessary ./worker export from vips package.json
- Remove jest config mapping for vips/worker
- Remove E2E test that relies on vips library functionality
- Fix prettier formatting in wordpress-externals-plugin.mjs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Exclude vips package from Storybook build
The vips package uses WASM modules which Vite cannot handle directly
during the Storybook build. Exclude it from optimization and mark it
as external for SSR to prevent build failures.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Stub vips package for Storybook build
Add a Vite plugin that stubs the @wordpress/vips module with no-op
functions for Storybook. This prevents the WASM module loading error
during build since Vite cannot handle WASM imports directly without
additional plugins.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Extend vips stub to cover wasm-vips and WASM files
Add enforce: 'pre' to run the plugin before others, and extend
stubbing to cover wasm-vips package and all .wasm file imports.
This ensures all WASM-related imports are stubbed before Vite
tries to process them.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* restore package lock from trunk
* update packages from trunk
* restore build file from trunk
* restore vips dependency
* restore vips dependency 2
* add vips dependency
* update readme
* try: avoid extra preload
* Fix preload path to include image_sizes and image_size_threshold
The preload test was failing because entities.js requests the root
endpoint with image_sizes and image_size_threshold fields, but
WordPress Core's site-editor.php preloads without these fields.
The previous fix added a filter to lib/experimental/media/load.php,
but this file is only loaded when the experimental media processing
feature is enabled, causing the filter to not run in most cases.
Move the preload filter to lib/compat/wordpress-7.0/preload.php which
is always loaded. Also update the wp-build templates to include these
fields for the experimental site-editor-v2 page.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix phpcs
* Fix WASM loading error by using module worker type
The ES6 version of wasm-vips uses import.meta for path resolution,
which requires the worker to be created as a module worker.
Classic script workers (the default) don't support import.meta,
causing the WASM module to fail loading with an undefined error.
By adding { type: 'module' } to the Worker constructor, import.meta
becomes available, and the ES6 version's locateFile callback works
correctly with our base64 data URL approach.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix wasm-vips loading in Blob URL Worker context
Add esbuild plugin to redirect wasm-vips ES module (vips-es6.js) to
CommonJS version (vips.js) which doesn't use import.meta.url that fails
in Blob URL Worker context.
Also:
- Upgrade wasm-vips from ^0.0.10 to ^0.0.16
- Add vips-heif.wasm inline import and locateFile handler
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove HEIF support from wasm-vips due to trademark issues
- Remove vips-heif.wasm import
- Add dynamicLibraries config to only load JXL module
- Remove HEIF handler from locateFile callback
HEIC/HEIF input support is removed, but JPEG, PNG, WebP, GIF,
AVIF output and JXL support remain unaffected.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add media processing fields directly to template preload paths
Move media processing fields (image_output_formats, jpeg_interlaced,
png_interlaced, gif_interlaced) from a filter in load.php to the
template files directly. This simplifies the code path and ensures
the preload paths match exactly with entities.js.
- Add fields to page.php.template and page-wp-admin.php.template
- Remove gutenberg_media_processing_preload_paths filter from load.php
- Update @see references in entities.js
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix CI failures: preload fields mismatch and Prettier formatting
- Add missing fields to preload.php to match entities.js exactly:
image_output_formats, jpeg_interlaced, png_interlaced, gif_interlaced
- Fix Prettier formatting in worker-build.mjs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* clean stray comment
* remove unintended changes
* Fix memory leak in vips by ensuring operation cleanup
Wrap vips operations in try/finally blocks to guarantee
inProgressOperations.delete(id) is always called, even when
errors occur during image processing.
* Rename isUploadingByParentId to hasPendingItemsByParentId
The selector checks for existence of items in the queue, not
their upload status. The new name better reflects its purpose.
* Fix test cleanup for mocked window.fetch
Properly restore the original fetch function in afterEach to
prevent test pollution.
* Clean up unused variable and update vips package reference
Remove unused $mime_type variable in REST attachments controller,
clarify comments in types, and use local file reference for the
@wordpress/vips package during development.
* Convert filesList to array before passing to addItems
FileList objects from input elements are not true arrays. Using
Array.from() ensures compatibility with store actions that expect
array methods to be available.
* Guard against duplicate upload-media store registration
The upload-media package is bundled into multiple packages
(block-editor, editor). This prevents errors when both bundles
are loaded on the same page by checking if the store is already
registered before calling register().
* restore loading vips prerelease directly
* add rotate image functionality
* update package lock
* correct vips load
* Add Jest stub for vips worker module
The worker-code.ts file is auto-generated during full builds and is
gitignored. Since unit tests don't run a full build, provide a stub
with mock implementations to prevent test failures.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix vips test to mock correct module path
The test was mocking @wordpress/vips but the actual code imports
from @wordpress/vips/worker, causing mock functions to not be used.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Skip server side sub size generation
* Add image processing concurrency constant
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 <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
* Add maxConcurrentImageProcessing to Settings type
Part of the image processing concurrency limiting work to
prevent OOM crashes during bulk image uploads.
* Wire image processing concurrency into reducer
Sets the default maxConcurrentImageProcessing value in the
upload-media store's DEFAULT_STATE so the concurrency limit
is active out of the box.
* Add image processing concurrency selectors
These selectors mirror the existing upload concurrency
selectors and will be used to enforce limits on concurrent
VIPS/WASM image processing operations.
* Gate image processing on concurrency limit
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.
* Trigger queued image processing when slot opens
Without this, items gated by the concurrency limit would
never be retried. Mirrors existing pattern where finished
uploads trigger pending upload items.
* Terminate VIPS worker when upload queue empties
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.
* Add tests for image processing selectors
Covers getActiveImageProcessingCount and
getPendingImageProcessing, verifying they correctly
identify ResizeCrop and Rotate operations.
* Add missing terminateVipsWorker to stub and mock
The Storybook vips stub and Jest test mock were not updated
when terminateVipsWorker was introduced, causing CI failures.
* Show spinner during sub-size image sideloading
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.
* Exclude generated worker-code.ts from ESLint
The vips package generates a 10MB+ worker-code.ts file
during builds that crashes ESLint's text-table formatter.
The file is already in .gitignore.
* Update package-lock.json for upload-media dep
* Lazy-load vips worker module on first use
The @wordpress/vips/worker module contains ~10MB of inlined
WASM code. Loading it eagerly at module parse time adds
unnecessary overhead when no image processing occurs. This
defers the import to the first actual vips function call
and caches the module for subsequent use.
* Add upload-media reference to block-library tsconfig
The block-library package depends on @wordpress/upload-media
but the tsconfig reference was missing, causing the
lint:tsconfig CI check to fail.
* Unbundle vips from upload-media as a script module
The upload-media IIFE bundle was 3.81MB because vips (with
inlined WASM) was bundled inline. Adding wpScriptModuleExports
to the vips package lets the build system externalize it,
producing a separate on-demand script module. Upload-media
drops to ~19KB; vips loads only when image processing runs.
* Make wasm-vips esbuild plugin generic via wpWorkers.resolve
The hardcoded createWasmVipsCommonJsPlugin in wp-build contained
package-specific knowledge about wasm-vips. This moves the resolve
config into the vips package's wpWorkers declaration and replaces
the plugin with a generic createModuleRedirectPlugin that reads
from the new config. Also documents the wpWorkers config format.
* Use ImageSizeCrop type for allImageSizes setting
The inline type had crop typed as just boolean, but
WordPress's add_image_size() also accepts a positional
array like ['left', 'top']. Reuse the existing
ImageSizeCrop interface which already has the correct type.
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>1 parent 21f985c commit 2453663
40 files changed
Lines changed: 2495 additions & 206 deletions
File tree
- lib
- compat/wordpress-7.0
- experimental/media
- packages
- block-editor/src/components/provider
- block-library
- src/image
- core-data/src
- editor/src/components/provider
- upload-media
- src/store
- test
- utils
- vips
- src
- wp-build
- lib
- templates
- storybook
- test/unit
- config
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
439 | 439 | | |
440 | 440 | | |
441 | 441 | | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
442 | 456 | | |
443 | 457 | | |
444 | 458 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
Lines changed: 44 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
81 | 101 | | |
82 | 102 | | |
83 | 103 | | |
84 | 104 | | |
| 105 | + | |
85 | 106 | | |
86 | 107 | | |
87 | 108 | | |
| |||
92 | 113 | | |
93 | 114 | | |
94 | 115 | | |
95 | | - | |
96 | | - | |
97 | 116 | | |
98 | 117 | | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
99 | 137 | | |
100 | 138 | | |
101 | 139 | | |
| |||
158 | 196 | | |
159 | 197 | | |
160 | 198 | | |
161 | | - | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
162 | 202 | | |
163 | 203 | | |
164 | 204 | | |
| |||
169 | 209 | | |
170 | 210 | | |
171 | 211 | | |
| 212 | + | |
172 | 213 | | |
173 | 214 | | |
174 | 215 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| 63 | + | |
63 | 64 | | |
64 | 65 | | |
65 | 66 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
| 21 | + | |
20 | 22 | | |
21 | 23 | | |
22 | 24 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| 129 | + | |
129 | 130 | | |
130 | 131 | | |
131 | 132 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
| |||
344 | 345 | | |
345 | 346 | | |
346 | 347 | | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
347 | 359 | | |
348 | 360 | | |
349 | 361 | | |
| |||
357 | 369 | | |
358 | 370 | | |
359 | 371 | | |
360 | | - | |
| 372 | + | |
361 | 373 | | |
362 | 374 | | |
363 | 375 | | |
| |||
448 | 460 | | |
449 | 461 | | |
450 | 462 | | |
| 463 | + | |
451 | 464 | | |
452 | 465 | | |
453 | 466 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
258 | 258 | | |
259 | 259 | | |
260 | 260 | | |
| 261 | + | |
261 | 262 | | |
262 | 263 | | |
263 | 264 | | |
| |||
874 | 875 | | |
875 | 876 | | |
876 | 877 | | |
877 | | - | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
878 | 881 | | |
879 | 882 | | |
880 | 883 | | |
| |||
1065 | 1068 | | |
1066 | 1069 | | |
1067 | 1070 | | |
1068 | | - | |
| 1071 | + | |
1069 | 1072 | | |
1070 | 1073 | | |
1071 | 1074 | | |
| |||
0 commit comments