Jetpack: defer Import and My Jetpack on front-end GETs (JETPACK-1747)#49936
Jetpack: defer Import and My Jetpack on front-end GETs (JETPACK-1747)#49936kraftbj wants to merge 6 commits into
Conversation
Jetpack::configure() ran $config->ensure( 'jitm' ) and $config->ensure( 'import' ) on every request, so both packages' PHP autoloaded into opcache even on plain front-end GET page views where they do no work. JITMs surface only as admin notices (current_screen) and a jetpack/v4/jitm REST endpoint; the Import package only registers jetpack/v4/import REST routes. Neither participates in rendering a front-end page. Gate both ensures so they still load on admin, cron, POST, WP-CLI, and REST requests (REST is initialized directly on rest_api_init, since the request type isn't known yet at plugins_loaded), and are skipped only on a plain front-end GET — the request where they are provably inert. This keeps their source out of opcache on the front-end hot path with no behavior change. Follows the same gating approach as the Stats packages. Other unconditionally-ensured packages (videopress, search, account_protection, waf) register front-end-GET or every-request surfaces and are left eager. See JETPACK-1747.
…th (JETPACK-1747) My Jetpack is a wp-admin dashboard, but Jetpack::late_initialization() ran My_Jetpack_Initializer::init() on every request. That init eagerly loads every product class (backup, boost, crm, protect, search, social, videopress, jetpack-ai, …) just to register admin plugin-action links, and calls JITM::configure() — roughly 13 files / ~160 KB of PHP pulled into opcache on a plain front-end GET page view where none of it runs. init() only registers admin-menu, admin_init, and rest_api_init surfaces; the two pieces that do immediate work, Connection REST authentication and Licensing, are already initialized unconditionally in Jetpack's constructor, so deferring this call leaves them untouched. Product activation sets/reads its transients via REST and admin_init only, never on a front-end GET. Gate the call through a shared should_load_admin_rest_only_packages() helper (also now used by the JITM/Import gating): run init() eagerly on admin, cron, POST, and WP-CLI requests, and on rest_api_init for REST (which can't be detected yet at plugins_loaded). A plain page view never fires rest_api_init, so My Jetpack — and the JITM package its init pulls in — stays unloaded there. This also makes the JITM gating effective: My Jetpack's init was the remaining front-end loader of JITM. The my-jetpack admin page (admin_menu), the jetpack/v4 REST endpoints (rest_api_init), and product-activation transients are all preserved. See JETPACK-1747.
…-1747) Code-review follow-up. Rename should_load_admin_rest_only_packages() to should_eager_load_packages() and rewrite its docblock summary so the "admin/REST-only" framing no longer reads as if REST returns true. The helper returns true for admin/cron/POST/WP-CLI (eager load now) and false for both a plain front-end GET and a REST request — REST is handled by the callers' rest_api_init deferral, not by this predicate. Behavior is unchanged; this is a naming/readability fix only.
…ts (JETPACK-1747) Adversarial review follow-up. Stop gating the JITM package in configure() and load it on every request as before; gate only the Import package. JITM's register() adds a jetpack_sync_before_send_updated_option filter that records the jetpack_last_plugin_sync transient, which JITM uses to invalidate its message-envelope cache after a plugin change. A Jetpack Sync send can fire on a plain front-end GET — including the dedicated-sync spawn-sync request, which is a wp_remote_get() handled on `init` and exits before rest_api_init ever fires. Deferring JITM off the front-end GET path left that filter unregistered for those sends, so the transient could miss an update and leave stale JITMs until the cache TTL. The Import package has no such always-on side effect (REST routes only), so it stays gated. Dropping JITM from the gate also removes the rest_api_init closure that duplicated Config::enable_jitm()'s namespace-fallback and double-invoked JITM::configure() on REST. The My Jetpack deferral — the bulk of the opcache win — is unchanged. Add Jetpack_Eager_Load_Packages_Test covering should_eager_load_packages(): admin, POST (incl. lower-case), and cron eager-load; a plain front-end GET does not. See JETPACK-1747.
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! Jetpack plugin: The Jetpack plugin has different release cadences depending on the platform:
If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack. |
Code Coverage SummaryCoverage changed in 1 file.
|
|
@kraftbj Nice analysis here. The JITM-stays-eager call and the spawn-sync reasoning behind it stood out. A few things before I'd sign off, most of them small:
I can't check the footprint numbers from the diff, so I'm assuming your loopback measurements still hold now that JITM is out of scope. |
Proposed changes
Part of the JETPACK-1747 per-request PHP/opcache reduction work. This is the package-gating slice for two Jetpack surfaces that do not need to load on a plain front-end GET page view: the Import package and My Jetpack.
Before this change,
Jetpack::configure()eagerly ensured the Import package on every request, andJetpack::late_initialization()eagerly initialized My Jetpack on every request. That loaded admin/REST-only PHP into the front-end hot path even when neither surface could do work.This gates those two initializers so the only request that skips them is the one where they are inert: a plain front-end GET page view. Every other request type keeps loading them as before:
rest_api_init, because REST cannot be identified yet whenplugins_loadedruns.JITM intentionally stays eager. Its registration adds sync bookkeeping for
jetpack_last_plugin_sync, and dedicated-sync GET requests can run oninitand exit beforerest_api_init; deferring JITM would skip that bookkeeping.Measured impact
TODO: add measurement data before marking this ready for review.
Expected shape to confirm:
/wp-json/)rest_api_initrest_api_initReview
Ran multiple
/ce-code-reviewand Codex adversarial passes while shaping this. Fixes made during review include keeping JITM eager, preserving the Import package'sjetpack_feature_import_enabledlifecycle action in the deferred path, and adding focused PHPUnit coverage for the request-type gate plus deferred/eager Import and My Jetpack behavior.Related product discussion/links
Does this pull request change what data or activity we track or use?
No. The same Import and My Jetpack hooks/routes initialize on request types where they can do work; this only avoids loading them on plain front-end GET page views where they would no-op.
Testing instructions
get_included_files()/opcache harness to confirm the Import package and My Jetpack PHP no longer load on that request. Add the measured before/after numbers above before moving the PR out of draft./wp-json/and confirm the Import and My Jetpack REST surfaces still initialize.Other information
plugins/jetpack, typeother).class.jetpack.phpandJetpack_Eager_Load_Packages_Test.php.svn: E200030: sqlite[S11]: database disk image is malformed).