From d8890f15676109084b134878e56a1839f5cf99ae Mon Sep 17 00:00:00 2001 From: Enrico Battocchi Date: Wed, 29 Apr 2026 13:53:38 +0200 Subject: [PATCH] perf: lazy-load AI Content Planner from the block editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 27.6 release pushed the plugin zip past the 5 MB mark. The dominant cause was the AI Content Planner module being bundled twice: * statically imported from packages/js/src/initializers/block-editor-integration.js and the Sidebar/Metabox fills, so it landed in block-editor.js (+161 KB raw / +28 KB gzipped); * shipped a second time as the standalone js/dist/ai-content-planner.js entry (~177 KB raw / 54 KB gzipped). That bundle's initialize.js only exports initContentPlanner() and never self-bootstraps, so on Elementor and classic-editor pages — where the standalone bundle was the only one enqueued — the planner UI never actually mounted. The bundle was dead weight there. Convert the three import sites to dynamic import() / React.lazy + Suspense under a single webpackChunkName, then drop the standalone webpack entry and localize wpseoContentPlanner onto block-editor instead. The Elementor enqueue hook is removed too: it was only ever shipping the dead-weight bundle. Net js/dist gzipped delta vs 27.6: -25.8 KiB. The planner code now ships once, on demand, and block-editor.js is ~28 KiB gzipped lighter for users without the AI feature enabled. Co-Authored-By: Claude Opus 4.7 (1M context) --- config/webpack/paths.js | 1 - packages/js/src/components/fills/MetaboxFill.js | 13 ++++++++++--- packages/js/src/components/fills/SidebarFill.js | 13 ++++++++++--- .../js/src/initializers/block-editor-integration.js | 7 +++++-- .../user-interface/content-planner-integration.php | 8 +++----- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/config/webpack/paths.js b/config/webpack/paths.js index ed42efbbc78..d168fde0945 100644 --- a/config/webpack/paths.js +++ b/config/webpack/paths.js @@ -53,7 +53,6 @@ const getEntries = ( sourceDirectory = "./packages/js/src" ) => ( { workouts: `${ sourceDirectory }/workouts.js`, "frontend-inspector-resources": `${ sourceDirectory }/frontend-inspector-resources.js`, "ai-generator": `${ sourceDirectory }/ai-generator/initialize.js`, - "ai-content-planner": `${ sourceDirectory }/ai-content-planner/initialize.js`, "ai-consent": `${ sourceDirectory }/ai-consent/initialize.js`, plans: `${ sourceDirectory }/plans/initialize.js`, } ); diff --git a/packages/js/src/components/fills/MetaboxFill.js b/packages/js/src/components/fills/MetaboxFill.js index 8b832b29fdb..4b77f13ba63 100644 --- a/packages/js/src/components/fills/MetaboxFill.js +++ b/packages/js/src/components/fills/MetaboxFill.js @@ -1,6 +1,6 @@ /* External dependencies */ import { useSelect } from "@wordpress/data"; -import { Fragment } from "@wordpress/element"; +import { Fragment, lazy, Suspense } from "@wordpress/element"; import { Fill } from "@wordpress/components"; import { __ } from "@wordpress/i18n"; import PropTypes from "prop-types"; @@ -24,9 +24,14 @@ import { BlackFridayPromotion } from "../BlackFridayPromotion"; import { withMetaboxWarningsCheck } from "../higherorder/withMetaboxWarningsCheck"; import isBlockEditor from "../../helpers/isBlockEditor"; import useToggleMarkerStatus from "./hooks/useToggleMarkerStatus"; -import ContentPlannerEditorItem from "../../ai-content-planner/containers/content-planner-editor-item"; import { EditorIntro } from "../EditorIntro"; +// Lazy-loaded so the planner module is not bundled into block-editor.js. +const ContentPlannerEditorItem = lazy( () => import( + /* webpackChunkName: "ai-content-planner-editor" */ + "../../ai-content-planner/containers/content-planner-editor-item" +) ); + const BlackFridayPromotionWithMetaboxWarningsCheck = withMetaboxWarningsCheck( BlackFridayPromotion ); /* eslint-disable complexity */ @@ -77,7 +82,9 @@ export default function MetaboxFill( { settings } ) { ) } { isPost && isBlockEditorActive && isAiFeatureActive && - + + + } { settings.isKeywordAnalysisActive && import( + /* webpackChunkName: "ai-content-planner-editor" */ + "../../ai-content-planner/containers/content-planner-editor-item" +) ); + /* eslint-disable complexity */ /** * Creates the SidebarFill component. @@ -66,7 +71,9 @@ export default function SidebarFill( { settings } ) { { isPost && isBlockEditorActive && isAiFeatureActive && - + + + } { settings.isKeywordAnalysisActive && initContentPlanner() ); } const yoastTab = getQueryArg( window.location.href, "yoast-tab" ); diff --git a/src/ai/content-planner/user-interface/content-planner-integration.php b/src/ai/content-planner/user-interface/content-planner-integration.php index dd399c599f6..75d40e032ce 100644 --- a/src/ai/content-planner/user-interface/content-planner-integration.php +++ b/src/ai/content-planner/user-interface/content-planner-integration.php @@ -61,8 +61,6 @@ public function __construct( */ public function register_hooks() { \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); - // Enqueue after Elementor_Premium integration, which re-registers the assets. - \add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'enqueue_assets' ], 11 ); } /** @@ -77,12 +75,12 @@ public function get_script_data(): array { } /** - * Localizes the content planner script data. + * Localizes the content planner script data onto the block-editor bundle, + * which lazy-loads the planner module on demand. * * @return void */ public function enqueue_assets() { - $this->asset_manager->enqueue_script( 'ai-content-planner' ); - $this->asset_manager->localize_script( 'ai-content-planner', 'wpseoContentPlanner', $this->get_script_data() ); + $this->asset_manager->localize_script( 'block-editor', 'wpseoContentPlanner', $this->get_script_data() ); } }