-
Notifications
You must be signed in to change notification settings - Fork 113
Add check for editor-only dependencies loaded on the frontend #1366
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
Open
ishitaj34
wants to merge
4
commits into
WordPress:trunk
Choose a base branch
from
ishitaj34:fix/issue-1360
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
9093ec8
add: check for editor-only dependencies loaded on the frontend
ishitaj34 8ede43f
add: test fixtures for editor dependencies check
ishitaj34 45b84cc
add: tests for Editor_Dependencies_Check
ishitaj34 3c66e4b
chore: refine comments and test fixtures
ishitaj34 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
306 changes: 306 additions & 0 deletions
306
includes/Checker/Checks/Performance/Editor_Dependencies_Check.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,306 @@ | ||
| <?php | ||
| /** | ||
| * Class Editor_Dependencies_Check. | ||
| * | ||
| * @package plugin-check | ||
| */ | ||
|
|
||
| namespace WordPress\Plugin_Check\Checker\Checks\Performance; | ||
|
|
||
| use Exception; | ||
| use WordPress\Plugin_Check\Checker\Check_Categories; | ||
| use WordPress\Plugin_Check\Checker\Check_Result; | ||
| use WordPress\Plugin_Check\Checker\Checks\Abstract_Runtime_Check; | ||
| use WordPress\Plugin_Check\Checker\Preparations\Demo_Posts_Creation_Preparation; | ||
| use WordPress\Plugin_Check\Checker\With_Shared_Preparations; | ||
| use WordPress\Plugin_Check\Traits\Amend_Check_Result; | ||
| use WordPress\Plugin_Check\Traits\Stable_Check; | ||
| use WordPress\Plugin_Check\Traits\URL_Aware; | ||
|
|
||
| /** | ||
| * Check for editor dependencies loaded on the frontend. | ||
| */ | ||
| class Editor_Dependencies_Check extends Abstract_Runtime_Check implements With_Shared_Preparations { | ||
|
|
||
| use Amend_Check_Result; | ||
| use Stable_Check; | ||
| use URL_Aware; | ||
|
|
||
| /** | ||
| * List of viewable post types. | ||
| * | ||
| * @since 1.0.2 | ||
| * @var array | ||
| */ | ||
| private $viewable_post_types; | ||
|
|
||
| /** | ||
| * Prevent duplicate warnings across multiple URLs. | ||
| * | ||
| * @var array | ||
| */ | ||
| private $reported_handles = array(); | ||
|
|
||
| /** | ||
| * Editor-only dependencies. | ||
| * | ||
| * @var string[] | ||
| */ | ||
| private const EDITOR_DEPENDENCIES = array( | ||
| 'wp-block-editor', | ||
| 'wp-editor', | ||
| 'wp-edit-post', | ||
| 'wp-edit-site', | ||
| ); | ||
|
|
||
| /** | ||
| * Gets the categories for the check. | ||
| * | ||
| * Every check must have at least one category. | ||
| * | ||
| * @since 1.0.2 | ||
| * | ||
| * @return array The categories for the check. | ||
| */ | ||
| public function get_categories() { | ||
| return array( Check_Categories::CATEGORY_PERFORMANCE ); | ||
| } | ||
|
|
||
| /** | ||
| * Runs this preparation step for the environment and returns a cleanup function. | ||
| * | ||
| * @since 1.0.2 | ||
| * | ||
| * @return callable Cleanup function to revert any changes made here. | ||
| * | ||
| * @throws Exception Thrown when preparation fails. | ||
| */ | ||
| public function prepare() { | ||
| $orig_scripts = isset( $GLOBALS['wp_scripts'] ) ? $GLOBALS['wp_scripts'] : null; | ||
|
|
||
| // Backup the original values for the global state. | ||
| $this->backup_globals(); | ||
|
|
||
| return function () use ( $orig_scripts ) { | ||
| if ( is_null( $orig_scripts ) ) { | ||
| unset( $GLOBALS['wp_scripts'] ); | ||
| } else { | ||
| $GLOBALS['wp_scripts'] = $orig_scripts; | ||
| } | ||
|
|
||
| $this->restore_globals(); | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Returns an array of shared preparations for the check. | ||
| * | ||
| * @since 1.0.2 | ||
| * | ||
| * @return array Returns a map of $class_name => $constructor_args pairs. If the class does not | ||
| * need any constructor arguments, it would just be an empty array. | ||
| */ | ||
| public function get_shared_preparations() { | ||
| $demo_posts = array_map( | ||
| static function ( $post_type ) { | ||
| return array( | ||
| 'post_title' => "Demo {$post_type} post", | ||
| 'post_content' => 'Test content', | ||
| 'post_type' => $post_type, | ||
| 'post_status' => 'publish', | ||
| ); | ||
| }, | ||
| $this->get_viewable_post_types() | ||
| ); | ||
|
|
||
| return array( | ||
| Demo_Posts_Creation_Preparation::class => array( $demo_posts ), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Runs the check on the plugin and amends results. | ||
| * | ||
| * @since 1.0.2 | ||
| * | ||
| * @param Check_Result $result The check results to amend and the plugin context. | ||
| */ | ||
| public function run( Check_Result $result ) { | ||
| $this->run_for_urls( | ||
| $this->get_urls(), | ||
| function ( $url ) use ( $result ) { | ||
| $this->check_url( $result, $url ); | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the list of URLs to run this check for. | ||
| * | ||
| * @since 1.0.2 | ||
| * | ||
| * @return array List of URL strings (either full URLs or paths). | ||
| * | ||
| * @throws Exception Thrown when a post type URL cannot be retrieved. | ||
| */ | ||
| protected function get_urls() { | ||
| $urls = array( home_url( '/' ), get_search_link(), get_author_posts_url( 1 ) ); | ||
| foreach ( $this->get_viewable_post_types() as $post_type ) { | ||
| $args = array( | ||
| 'posts_per_page' => 1, | ||
| 'post_type' => $post_type, | ||
| 'post_status' => array( 'publish', 'inherit' ), | ||
| 'ignore_sticky_posts' => true, | ||
| 'no_found_rows' => true, | ||
| 'lazy_load_term_meta' => false, | ||
| 'update_post_meta_cache' => false, | ||
| 'update_post_term_cache' => false, | ||
| ); | ||
|
|
||
| $the_query = new \WP_Query( $args ); | ||
|
|
||
| if ( $the_query->have_posts() ) { | ||
| while ( $the_query->have_posts() ) { | ||
| $the_query->the_post(); | ||
|
|
||
| $urls[] = get_permalink(); | ||
| $post = get_post(); | ||
| $taxonomy_names = get_post_taxonomies( $post ); | ||
| foreach ( $taxonomy_names as $taxonomy_name ) { | ||
| if ( ! is_taxonomy_viewable( $taxonomy_name ) ) { | ||
| continue; | ||
| } | ||
|
|
||
| $terms = get_the_terms( $post, $taxonomy_name ); | ||
| if ( ! is_array( $terms ) ) { | ||
| continue; | ||
| } | ||
| foreach ( $terms as $term ) { | ||
| $term_link = get_term_link( $term ); | ||
| if ( ! is_wp_error( $term_link ) ) { | ||
| $urls[] = $term_link; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| throw new Exception( | ||
| sprintf( | ||
| /* translators: %s: The Post Type name. */ | ||
| __( 'Unable to retrieve post URL for post type: %s', 'plugin-check' ), | ||
| $post_type | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| /* Restore original Post Data */ | ||
| wp_reset_postdata(); | ||
| } | ||
|
|
||
| return $urls; | ||
| } | ||
|
|
||
| /** | ||
| * Amends the given result by running the check for the given URL. | ||
| * | ||
| * @since 1.0.2 | ||
| * | ||
| * @param Check_Result $result The check result to amend, including the plugin context to check. | ||
| * @param string $url URL to run the check for. | ||
| * | ||
| * @throws Exception Thrown when the check fails with a critical error (unrelated to any errors detected as part of | ||
| * the check). | ||
| */ | ||
| protected function check_url( Check_Result $result, $url ) { | ||
| // Reset the wp_scripts instance. | ||
| unset( $GLOBALS['wp_scripts'] ); | ||
|
|
||
| /* | ||
| * Run the 'wp_enqueue_script' action, wrapped in an output buffer in case of any callbacks printing scripts | ||
| * directly. This is discouraged, but some plugins or themes are still doing it. | ||
| */ | ||
| $wp_scripts = wp_scripts(); | ||
| ob_start(); | ||
| wp_enqueue_scripts(); | ||
| $wp_scripts->do_head_items(); | ||
| $wp_scripts->do_footer_items(); | ||
| ob_get_clean(); | ||
|
|
||
| foreach ( $wp_scripts->done as $handle ) { | ||
| $script = $wp_scripts->registered[ $handle ]; | ||
|
|
||
| if ( isset( $this->reported_handles[ $handle ] ) ) { | ||
| continue; | ||
| } | ||
|
|
||
| if ( ! $script->src || strpos( $script->src, $result->plugin()->url() ) !== 0 ) { | ||
| continue; | ||
| } | ||
|
|
||
| $script_path = str_replace( $result->plugin()->url(), $result->plugin()->path(), $script->src ); | ||
|
|
||
| foreach ( $script->deps as $dependency ) { | ||
| if ( in_array( $dependency, self::EDITOR_DEPENDENCIES, true ) ) { | ||
| $this->add_result_warning_for_file( | ||
| $result, | ||
| sprintf( | ||
| /* translators: %s: dependency name */ | ||
| __( | ||
| 'This script loaded on the frontend depends on the editor-only package %s. If it is only needed in the block editor, consider using enqueue_block_editor_assets instead.', | ||
| 'plugin-check' | ||
| ), | ||
| $dependency | ||
| ), | ||
| 'EditorDependencies.EditorPackageDependency', | ||
| $script_path | ||
| ); | ||
|
|
||
| $this->reported_handles[ $handle ] = true; | ||
|
|
||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Returns an array of viewable post types. | ||
| * | ||
| * @since 1.0.2 | ||
| * | ||
| * @return array Array of viewable post type slugs. | ||
| */ | ||
| private function get_viewable_post_types() { | ||
| if ( ! is_array( $this->viewable_post_types ) ) { | ||
| $this->viewable_post_types = array_filter( get_post_types(), 'is_post_type_viewable' ); | ||
| } | ||
|
|
||
| return $this->viewable_post_types; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the description for the check. | ||
| * | ||
| * Every check must have a short description explaining what the check does. | ||
| * | ||
| * @since 1.1.0 | ||
| * | ||
| * @return string Description. | ||
| */ | ||
| public function get_description(): string { | ||
| return __( 'Checks whether scripts loaded on the frontend depend on editor-only WordPress packages.', 'plugin-check' ); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the documentation URL for the check. | ||
| * | ||
| * Every check must have a URL with further information about the check. | ||
| * | ||
| * @since 1.1.0 | ||
| * | ||
| * @return string The documentation URL. | ||
| */ | ||
| public function get_documentation_url(): string { | ||
| return 'https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/'; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-with-error/load.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <?php | ||
| /** | ||
| * File contains errors for the editor dependencies check. | ||
| * | ||
| * @package plugin-check | ||
| */ | ||
|
|
||
| add_action( | ||
| 'enqueue_block_assets', | ||
| function() { | ||
| wp_enqueue_script( | ||
| 'editor-dependency-script', | ||
| plugin_dir_url( __FILE__ ) . 'script.js', | ||
| array( 'wp-block-editor' ), | ||
| '1.0' | ||
| ); | ||
|
|
||
| wp_enqueue_script( | ||
| 'editor-dependency-script-2', | ||
| plugin_dir_url( __FILE__ ) . 'script2.js', | ||
| array( 'wp-edit-site' ), | ||
| '1.0' | ||
| ); | ||
| } | ||
| ); |
1 change: 1 addition & 0 deletions
1
tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-with-error/script.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| console.log('test'); |
Empty file.
18 changes: 18 additions & 0 deletions
18
tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-without-error/load.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <?php | ||
| /** | ||
| * File without errors for the editor dependencies check. | ||
| * | ||
| * @package plugin-check | ||
| */ | ||
|
|
||
| add_action( | ||
| 'enqueue_block_assets', | ||
| function() { | ||
| wp_enqueue_script( | ||
| 'normal-script', | ||
| plugin_dir_url( __FILE__ ) . 'script.js', | ||
| array( 'wp-blocks' ), | ||
| '1.0' | ||
| ); | ||
| } | ||
| ); |
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Usually such a hardcoded list is too static. It misses a lot of editor-only scripts and will become stale very quickly. That said, editor, edit-post and edit-site are entry point scripts in the admin which definitely don't make sense on the frontend. But I highly doubt a plugin would be enqueueing those on the frontend. And block-editor can definitely be legitimately enqueued on the frontend (e.g. to enable the block editor for the comment form).
So I'd say this part here needs more discussion for sure.
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.
My thinking was that the issue specifically mentioned dependencies such as
wp-block-editor, so I started with a small set of packages that seemed most closely tied to editor functionality rather than trying to classify all Gutenberg packages.I agree that maintaining a hardcoded list is problematic, and
wp-block-editoritself can have frontend uses so the intention wasn't to treat these dependencies as definitively editor-only, but rather as a signal that the script may have been intended only for the block editor.The signal itself probably needs to be reconsidered rather than expanding the list indefinitely.