-
Notifications
You must be signed in to change notification settings - Fork 35
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
Feature/svgo support #79
Conversation
@Sidsector9 @darylldoyle looking for review from you both on this, thanks! |
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.
@jeffpaul I am wondering if we can extend this to a separate feature to support a batch optimiser, which will optimise already uploaded SVG images, what do you think? |
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.
Thank you very much for working on this PR.
✅ Tested by uploading a 1KB SVG using the Media Uploader. The size got reduced to 411 bytes.
✅ Tested the filter add_filter( 'safe_svg_svgo_params', '__return_false' );
to disable optimisation, works as excpected.
❌ Direct upload using core/image block did not optimise the SVG
This is the image I used:
Please watch the video below:
SVGO.mov
Hi @Sidsector9 Thanks for testing this. This is something that I should have mentioned in the PR (I've updated the description): the image will get optimized on post save. So, if you click to update the post, the image should get optimized. |
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.
Thanks for the additional details @gsarig. LGTM, approving 👍
@Sidsector9 @jeffpaul My thought here was to allow it to optimize existing SVGs when someone triggers a batch regenerate of images, with WP-CLI or a third-party plugin. That way we wouldn't have to build a settings page for that, or a specific mechanism from scratch and the implementation should be quicker. |
@gsarig yeah a feature like that would be a good addition to the plugin's capabilities. We can open a separate issue to investigate how best to approach this. |
@vikrampm1 can you please open an issue as noted by Sid and Giorgos above? |
assets/js/admin/admin.js
Outdated
const svgUrl = changedBlock?.attributes?.url; | ||
|
||
// Run only if the image is an SVG that has not been optimized yet. | ||
if (!svgUrl || !svgUrl.endsWith('.svg')) { |
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.
thought (non-blocking): This will only pick up files with the .svg
extension. What about .svgz
or SVGs that are uploaded without the SVG extension?
Do we have access to the mime type at this point to check that instead?
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.
@darylldoyle We don't (that's the reason I made the check based on the filename). Here's an example of what the changedBlock returns:
One approach that I can think of is to remove that particular check and perform it inside the fetch
, where the file's content-type
becomes available. In that case, though, the fetch
would have to run on every file, while now we only run it on .svg
files. Perhaps we could mitigate that by narrowing it down to specific extensions (e.g. svg*
for anything that ends in svg-something) and files with no extension at all. That way, we wouldn't bother running it on jpgs, pngs etc.
What do you think?
assets/js/admin/admin.js
Outdated
for (const changedBlock of changes.blocks) { | ||
|
||
// Run only if this is a new core image block. | ||
if ('core/image' === changedBlock.name && !changedBlock?.originalContent) { |
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.
thought (non-blocking): This only works for core/image
blocks. What about core/gallery
, core/media-text
etc.
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.
@darylldoyle I've pushed a new commit, which adds support for core/media-test
and for nested blocks containing images (which covers the core/gallery
scenario).
Can you please take a look?
Thanks!
assets/js/admin/admin.js
Outdated
subscribe(() => { | ||
if (isSavingPost()) { |
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.
thought (non-blocking): The handling of this on save concerns me a little. We're making 2 additional requests for each image on the page. That could add up quickly and really bog down the save process of the block editor.
Have we looked other options, such as something like https://github.com/spatie/image-optimizer which may allow us to do the optimisation from PHP at upload time.
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.
@darylldoyle I agree with your arguments, and this is something that concerned me as well before I ended up with the specific approach. When testing the image-optimizer
, though, I came across the following issues, which made me avoid it:
- It uses SGVO 1, which is the older version of SVGO, and based on that comment, it seems to have a vulnerable dependency.
- When I tried to test it, I got a fatal error, as one of the tool's dependencies seem to require PHP 8.1.0. (The error was
Your Composer dependencies require a PHP version ">= 8.1.0".
.
I do agree, that a PHP-based approach would be ideal, and much simpler to implement. I didn't find a reliable tool to use instead, though.
@gsarig some input from Daryll above, let me know if you hope to be able to react to those or if I should pull someone additional to help finish this off? |
@darylldoyle @Sidsector9 looking to either for a review on the updates here |
dabd617
to
e041baf
Compare
e041baf
to
301de4d
Compare
c317cb0
to
37688c2
Compare
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.
I've added a few notes inline.
While manually testing, I'm not seeing the changes been saved while logged in as an administrator.
I uploaded an SVG with a bunch of needless <g>
tags, the optimiser picks it up client side and sends the network request, but the file isn't been updated.
assets/js/admin/admin.js
Outdated
import {select, subscribe} from '@wordpress/data'; | ||
|
||
(function () { | ||
const ajaxUrl = new URL(safeSvgParams.ajaxUrl); // eslint-disable-line no-undef |
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.
Rather than disable the rule each time, I'd suggest defining the global in a comment at the start of the file
/* global safeSvgParams */
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.
@peterwilsoncc Done
assets/js/admin/admin.js
Outdated
form.addEventListener('submit', function (event) { | ||
const fileInput = document.querySelector('#async-upload'); | ||
if (fileInput.files[0].type === 'image/svg+xml' || fileInput.files[0].name.endsWith('.svg')) { | ||
document.cookie = safeSvgCookie + '=1;' + safeSvgCookieAttr; |
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.
WordPress includes wpCookies
with a bunch of cookie helper functions, it's enqueued on most pages in the admin so you should be able to use that.
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.
Thanks for that tip @peterwilsoncc I've updated the code to use wpCookies
. I couldn't find much documentation about wpCookies
so I went to the core to see how it works. Can you please take a look to check if the implementation is good?
d6c933c
to
5443373
Compare
c63a78b
to
be63c0b
Compare
be63c0b
to
5737372
Compare
Thanks for the feedback @peterwilsoncc I've been testing it with unoptimized SVGs taken from here, as well as with the One additional thing that I noticed was that Cypress reported a problem with a failed test: I searched in the SVGO library for any option to allow specific tags and attributes, but I couldn't find something. So, what I ended up doing was adding an additional check (see here) to disable the optimizer if a dev has added allowed tags or attributes. Please let me know what you think about that and if you have any other suggestions. |
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.
I've added a few more notes inline, including why the optimization was failing when I tested earlier.
In the JS, it should be possible to use the attachments ID and pass that to your ajax request in place of the URL. That will make the admin-ajax function a little more straight forward.
includes/optimizer.php
Outdated
if ( empty( $parsed_url['path'] ) ) { | ||
return false; | ||
} | ||
$file = ABSPATH . ltrim( $parsed_url['path'], '/' ); |
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.
I figured out why I wasn't getting the optimised version of the image.
I was testing on a site running WP in sub-directory mode with the directory structure:
/ #root directory
/content #content directory
/wp # WordPress install.
Instead of path/to/content/.../file.svg
the $file
is calculated as path/to/wp/content/.../file.svg
.
In the method above, I've suggested you use get_attached_file()
which will remove the need for this method.
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.
Thanks for that @peterwilsoncc I followed your suggestion and on my local tests it seems to be working.
includes/optimizer.php
Outdated
*/ | ||
public function optimize() { | ||
$svg_url = filter_input( INPUT_GET, 'svg_url', FILTER_SANITIZE_URL ); | ||
if ( ! current_user_can( 'edit_posts', attachment_url_to_postid( $svg_url ) ) ) { |
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.
Meta capabilities (ie, for the specific post) use the singular.
if ( ! current_user_can( 'edit_posts', attachment_url_to_postid( $svg_url ) ) ) { | |
if ( ! current_user_can( 'edit_post', attachment_url_to_postid( $svg_url ) ) ) { |
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.
You are right, I missed that. This should be fixed now.
includes/optimizer.php
Outdated
); | ||
$params = wp_json_encode( | ||
[ | ||
'ajaxUrl' => esc_url( admin_url( 'admin-ajax.php' ) ), |
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.
I'm unsure, but I a suspect this would be better as sanitize_url()
, we're wanting to make sure the URL is safe -- json-encode makes sure it renders correctly for JS.
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.
sanitize_url()
has been deprecated, and WP suggests using esc_url_raw()
instead, so that's what I changed it to.
includes/optimizer.php
Outdated
$params = wp_json_encode( | ||
[ | ||
'ajaxUrl' => esc_url( admin_url( 'admin-ajax.php' ) ), | ||
'svgoParams' => wp_json_encode( $this->svgo_params() ), |
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.
I'm confused by this, why aren't you passing this as is to the outer wp_json_encode call -- then you won't need to call JSON.parse
in the JavaScript file.
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.
Fixed
2d71745
to
94316e2
Compare
Thank you for the great feedback @peterwilsoncc |
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.
LGTM, thanks for your patience during the review.
I noticed the optimize ajax request was been made twice on each save when I uploaded directly in to the block editor but have not been able to figure out why. It appears the wp.data
subscription runs twice but that doesn't seem like something we have control over.
I think this is good to merge but attach a gif of what I am seeing in case inspiration strikes you.
I'd love to know what sort of attributes we're seeing stripped here. This is something I was looking into with the previous Pro implementation of SVGO and I have a feeling there are ways around it using SVGO plugins For example, this is the plugin that removes the unknowns. The knowns are stored in a collections file. Perhaps we could extend that with our custom properties https://github.com/svg/svgo/blob/main/plugins/removeUnknownsAndDefaults.js |
Description of the Change
Implements support for SVG optimization using the popular SVGO tool. For the scripts' structure and build, 10up-toolkit has been utilized. The Optimizer is active by default and will try to optimize any SVG file uploaded via the Media Library, using the default SVGO settings.
A filter
safe_svg_svgo_param
has been added so that developers can override these settings and pass their own.Also, using the same filter like this:
add_filter( 'safe_svg_svgo_params', '__return_false' );
will allow them to completely disable the Optimizer.At the moment, the optimizer doesn't work if you upload an image directly (using "Upload" instead of the "Media Library" button), as it bypasses the media uploader.Other than that, as long as the file is uploaded using the Media Library, it will get optimized. Also, it should work with both single and multiple images. With the first chance, I will resume my work to make it work with direct uploads as well.Closes #68
How to test the Change
npm install
andnpm run build
.Known issue: Given that SVGO is a JS tool, using it with WordPress had its challenges. To make it work, a hook to
wp.Uploader
has been made, to identify when a file gets uploaded, optimize it, and then make an AJAX call to update the file with PHP.Therefore, at the moment it doesn't work if you upload an image directly, bypassing the media uploader. More specifically, if you hit the "Upload" button from the image below:Using the "Media Library" option, on the other hand, should work with either single or multiple SVG. It should also work with drag and drop and even if you drop mixed image types (for example SVGs together with JPEGs, PNGs etc) at the same time.Update: Support for optimizing on direct uploads has been added on this commit, and images uploaded using the direct upload button will get optimized when the post is saved.
Changelog Entry
Credits
Props @gsarig
Checklist: