-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add wp-api/speculation scaffold #56
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
Adi-ty
wants to merge
4
commits into
main
Choose a base branch
from
v1.0.0/task/wp-api-speculation
base: main
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
13a1d94
feat: add wp-api/speculation scaffold
Adi-ty cbae87a
test: call speculation filter methods directly, drop 6.8 skips
Adi-ty c2c8b50
fix: correct speculation scaffold core-behaviour claims
Adi-ty 6dea952
fix: graft PSR-4 root directory onto path inputs in discover_from
Adi-ty 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
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
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
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
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
78 changes: 78 additions & 0 deletions
78
node-packages/wp-tooling/scaffolds/wp-api/speculation/scaffold.json
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,78 @@ | ||
| { | ||
| "slug": "speculation", | ||
| "category": "wp-api", | ||
| "name": "Speculative Loading", | ||
| "description": "Tunes WordPress core's Speculation Rules API (core since WP 6.8). Generates a Registrable that pins the prefetch/prerender mode and eagerness through the 'wp_speculation_rules_configuration' filter and excludes paths through 'wp_speculation_rules_href_exclude_paths', guarded so it no-ops on WP < 6.8.", | ||
| "source": "template", | ||
| "wizard_step": "wp-apis", | ||
| "inputs": [ | ||
| { | ||
| "key": "namespace", | ||
| "description": "PSR-4 namespace for the service class. Default 'Inc\\Services' matches the rtCamp skeleton.", | ||
| "discover_from": "composer.json:autoload.psr-4", | ||
| "default": "Inc\\Services" | ||
| }, | ||
| { | ||
| "key": "base_path", | ||
| "description": "Directory for the service file. Default 'includes/Services'.", | ||
| "discover_from": "composer.json:autoload.psr-4", | ||
| "default": "includes/Services" | ||
| }, | ||
| { | ||
| "key": "tests_namespace", | ||
| "description": "PSR-4 namespace for the test class. Default 'Inc\\Tests\\Services'.", | ||
| "discover_from": "composer.json:autoload.psr-4", | ||
| "default": "Inc\\Tests\\Services" | ||
| }, | ||
| { | ||
| "key": "tests_path", | ||
| "description": "Directory for the test file. Default 'tests/Services'.", | ||
| "default": "tests/Services" | ||
| }, | ||
| { | ||
| "key": "name", | ||
| "description": "Logical service name. Normalised to a PascalCase class name, so 'speculative-loading' and 'SpeculativeLoading' both yield class SpeculativeLoading.", | ||
| "required": true | ||
| }, | ||
| { | ||
| "key": "class", | ||
| "description": "PascalCase class name, derived from name.", | ||
| "discover_from": "input:name", | ||
| "transform": "pascal-case" | ||
| }, | ||
| { | ||
| "key": "mode", | ||
| "description": "Speculative loading mode written into the MODE constant. One of 'prefetch' (fetch the document early) or 'prerender' (render it in a hidden tab). 'auto' hands the choice back to core, which resolves it to 'prefetch'.", | ||
| "default": "prerender" | ||
| }, | ||
| { | ||
| "key": "eagerness", | ||
| "description": "How keenly the browser acts on the rules, written into the EAGERNESS constant. One of 'conservative' (on pointer/touch down), 'moderate' (on hover, this scaffold's default) or 'eager' (as soon as the link is known). 'auto' hands the choice back to core, which resolves it to 'conservative'.", | ||
| "default": "moderate" | ||
| } | ||
| ], | ||
| "files": [ | ||
| { | ||
| "src": "templates/speculation.php.mustache", | ||
| "dest": "{{base_path}}/{{class}}.php" | ||
| } | ||
| ], | ||
| "wiring": [ | ||
| { | ||
| "target_file": "{{base_path}}/../Modules/Services.php", | ||
| "anchor": "// scaffold:wp/registrable:classes", | ||
| "snippet_template": "\\{{namespace}}\\{{class}}::class,", | ||
| "description": "Add the class to a Services (or performance-specific) module so the framework Loader instantiates it and calls register_hooks(). If no suitable module exists, scaffold one first via `wp-tooling add wp/module --name=Services --kind=registrable`." | ||
| } | ||
| ], | ||
| "tests": [ | ||
| { | ||
| "src": "templates/test.php.mustache", | ||
| "dest": "{{tests_path}}/{{class}}Test.php", | ||
| "framework": "phpunit" | ||
| } | ||
| ], | ||
| "composer_dependencies": { | ||
| "rtcamp/wp-framework": "^1.0" | ||
| } | ||
| } | ||
98 changes: 98 additions & 0 deletions
98
node-packages/wp-tooling/scaffolds/wp-api/speculation/templates/speculation.php.mustache
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,98 @@ | ||
| <?php | ||
| /** | ||
| * {{class}}: speculative loading rules (mode: {{mode}}, eagerness: {{eagerness}}). | ||
| * | ||
| * Scaffolded by @rtcamp/wp-tooling. | ||
| * | ||
| * @link https://github.com/rtCamp/wp-framework | ||
| * @link https://developer.wordpress.org/reference/functions/wp_get_speculation_rules/ | ||
| * | ||
| * @package {{namespace}} | ||
| */ | ||
|
|
||
| declare( strict_types = 1 ); | ||
|
|
||
| namespace {{namespace}}; | ||
|
|
||
| use rtCamp\WPFramework\Contracts\Interfaces\Registrable; | ||
|
|
||
| /** | ||
| * Customises WordPress core's Speculation Rules API (core since WP 6.8). | ||
| * | ||
| * Core defaults both settings to 'auto', which it resolves to prefetch with | ||
| * conservative eagerness. This class pins them instead, and keeps state-changing | ||
| * or personalised URLs out of the rules. The three constants are the tunable | ||
| * surface; the filter bodies rarely change. | ||
| */ | ||
| final class {{class}} implements Registrable { | ||
|
|
||
| /** | ||
| * Speculation mode: 'prefetch', 'prerender', or 'auto' to defer to core. | ||
| */ | ||
| public const MODE = '{{mode}}'; | ||
|
|
||
| /** | ||
| * Eagerness: 'conservative', 'moderate', 'eager', or 'auto' to defer to core. | ||
| */ | ||
| public const EAGERNESS = '{{eagerness}}'; | ||
|
|
||
| /** | ||
| * Paths never prefetched or prerendered, as URL path patterns. | ||
| * | ||
| * Prerendering executes the target page, so exclude anything that mutates | ||
| * state or is personalised, for example: | ||
| * [ '/cart/', '/checkout/', '/my-account/*' ]. | ||
| * | ||
| * @var string[] | ||
| */ | ||
| public const EXCLUDE_PATHS = []; | ||
|
|
||
| /** | ||
| * Bind the speculation rules filters. Called by the framework Loader. | ||
| * | ||
| * No-ops before WP 6.8, where the Speculation Rules API does not exist. | ||
| */ | ||
| public function register_hooks(): void { | ||
| if ( ! function_exists( 'wp_get_speculation_rules' ) ) { | ||
| return; | ||
| } | ||
|
|
||
| add_filter( 'wp_speculation_rules_configuration', [ $this, 'filter_configuration' ] ); | ||
| add_filter( 'wp_speculation_rules_href_exclude_paths', [ $this, 'filter_href_exclude_paths' ] ); | ||
| } | ||
|
|
||
| /** | ||
| * Pin the speculation mode and eagerness. | ||
| * | ||
| * A null $config means another filter switched speculative loading off, so it | ||
| * is returned untouched rather than revived. Any other non-array value falls | ||
| * outside the filter's contract and is likewise left alone. | ||
| * | ||
| * @param array<string, string>|null $config Core configuration, or null when disabled. | ||
| * @return array<string, string>|null Filtered configuration. | ||
| */ | ||
| public function filter_configuration( $config ) { | ||
| if ( ! is_array( $config ) ) { | ||
| return $config; | ||
| } | ||
|
|
||
| return [ | ||
| 'mode' => self::MODE, | ||
| 'eagerness' => self::EAGERNESS, | ||
| ]; | ||
| } | ||
|
|
||
| /** | ||
| * Add this project's paths to the href exclusion list. | ||
| * | ||
| * Core passes the current mode as a second argument; add it to the | ||
| * signature (and bump add_filter()'s accepted_args) to vary the | ||
| * exclusions between prefetch and prerender. | ||
| * | ||
| * @param string[] $paths Path patterns already excluded. | ||
| * @return string[] Path patterns with this project's exclusions merged in. | ||
| */ | ||
| public function filter_href_exclude_paths( array $paths ): array { | ||
| return array_values( array_unique( array_merge( $paths, self::EXCLUDE_PATHS ) ) ); | ||
| } | ||
| } |
105 changes: 105 additions & 0 deletions
105
node-packages/wp-tooling/scaffolds/wp-api/speculation/templates/test.php.mustache
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,105 @@ | ||
| <?php | ||
| /** | ||
| * Tests for {{namespace}}\{{class}}. | ||
| * | ||
| * Scaffolded by @rtcamp/wp-tooling. | ||
| * | ||
| * @link https://github.com/rtCamp/wp-framework | ||
| * | ||
| * @package {{tests_namespace}} | ||
| */ | ||
|
|
||
| declare( strict_types = 1 ); | ||
|
|
||
| namespace {{tests_namespace}}; | ||
|
|
||
| use {{namespace}}\{{class}}; | ||
| use WP_UnitTestCase; | ||
| use rtCamp\WPFramework\Contracts\Interfaces\Registrable; | ||
|
|
||
| /** | ||
| * Class {{class}}Test | ||
| * | ||
| * @covers \{{namespace}}\{{class}} | ||
| */ | ||
| final class {{class}}Test extends WP_UnitTestCase { | ||
|
|
||
| /** | ||
| * The class is a Registrable, so the framework Loader boots it. | ||
| */ | ||
| public function test_implements_registrable(): void { | ||
| $this->assertInstanceOf( Registrable::class, new {{class}}() ); | ||
| } | ||
|
|
||
| /** | ||
| * The filters are bound if, and only if, the Speculation Rules API exists. | ||
| * | ||
| * Before WP 6.8 there is nothing to customise, so register_hooks() must | ||
| * bind nothing rather than fatal. | ||
| */ | ||
| public function test_register_hooks_binds_the_filters_only_when_the_api_exists(): void { | ||
| ( new {{class}}() )->register_hooks(); | ||
|
|
||
| $api_exists = function_exists( 'wp_get_speculation_rules' ); | ||
| $config_bound = false !== has_filter( 'wp_speculation_rules_configuration' ); | ||
| $exclusion_bound = false !== has_filter( 'wp_speculation_rules_href_exclude_paths' ); | ||
|
|
||
| $this->assertSame( $api_exists, $config_bound ); | ||
| $this->assertSame( $api_exists, $exclusion_bound ); | ||
| } | ||
|
|
||
| /** | ||
| * The scaffolded constants are values core actually accepts. | ||
| * | ||
| * Core silently replaces an unknown mode or eagerness with its own default, | ||
| * so a typo would pin nothing at all. 'immediate' is left out on purpose: | ||
| * core rejects it for document-level rules. | ||
| */ | ||
| public function test_constants_are_valid_core_values(): void { | ||
| $this->assertContains( {{class}}::MODE, [ 'auto', 'prefetch', 'prerender' ] ); | ||
| $this->assertContains( | ||
| {{class}}::EAGERNESS, | ||
| [ 'auto', 'conservative', 'moderate', 'eager' ] | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * The configuration filter pins the scaffolded mode and eagerness. | ||
| */ | ||
| public function test_configuration_is_pinned_to_the_scaffolded_values(): void { | ||
| $config = ( new {{class}}() )->filter_configuration( | ||
| [ | ||
| 'mode' => 'auto', | ||
| 'eagerness' => 'auto', | ||
| ] | ||
| ); | ||
|
|
||
| $this->assertSame( | ||
| [ | ||
| 'mode' => {{class}}::MODE, | ||
| 'eagerness' => {{class}}::EAGERNESS, | ||
| ], | ||
| $config | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * A null configuration means speculative loading is off, and stays off. | ||
| */ | ||
| public function test_configuration_stays_disabled_when_it_is_null(): void { | ||
| $this->assertNull( ( new {{class}}() )->filter_configuration( null ) ); | ||
| } | ||
|
|
||
| /** | ||
| * The exclusion filter adds this project's paths without dropping others'. | ||
| */ | ||
| public function test_href_exclude_paths_keeps_paths_added_by_others(): void { | ||
| $paths = ( new {{class}}() )->filter_href_exclude_paths( [ '/existing/' ] ); | ||
|
|
||
| $this->assertContains( '/existing/', $paths ); | ||
|
|
||
| foreach ( {{class}}::EXCLUDE_PATHS as $excluded ) { | ||
| $this->assertContains( $excluded, $paths ); | ||
| } | ||
| } | ||
| } |
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.
Uh oh!
There was an error while loading. Please reload this page.