-
Notifications
You must be signed in to change notification settings - Fork 41
migration lazy window scripts
Who this affects: plugins whose native-window bundle does something at script-load time other than publish its render callback — installs an API on wp.os, starts a poller, subscribes to a shell hook, registers a dock decoration.
Who it does not: the overwhelming majority. A bundle that ends in
window.openStationNativeWindows[ 'my-plugin/inbox' ] = ( body, ctx ) => { … };and does nothing else needs no change at all. Neither does a wallpaper registered with openstation_register_wallpaper(), a widget registered with openstation_register_widget(), nor any plugin that only opens windows.
Every registered native window's script used to be enqueued on every admin page the shell rendered, and every registered wallpaper's and widget's script alongside it. On a stock install that was well over a megabyte of JavaScript — WP Explorer, Posts, Plugins, Comments, the Recycle Bin, Content Graph, Games, the agent runner, Living Tree, Snow, nine desktop widgets — downloaded and parsed before the user had clicked anything, for windows most sessions never open, wallpapers most users are not wearing, and widgets most desktops don't show.
Now:
-
A native window's
scriptloads the first time that window opens. The shell reads the render callback offwindow.openStationNativeWindows[ <id> ]after the load, which is why nothing is required of an ordinary bundle. The window shows its declared<template>immediately and its loading spinner covers the fetch. - A canvas wallpaper's script loads when the wallpaper is applied, or when the user opens the wallpaper picker. The shell registers a metadata-only stub from the payload — label, preview swatch, description — so the picker paints a tile with no bundle in the tab.
-
A widget's script loads when the widget mounts. Its def is assembled entirely from
openstation_register_widget()'s metadata; the bundle's only contribution ismount. An enabled widget still appears in the same beat as before, becausemountIfEnabled()runs immediately after registration.
Delivery is otherwise identical. wp_localize_script, wp_add_inline_script and wp_set_script_translations data is harvested off the registered handle into the boot payload and replayed as inline <script> tags around the injected <script src>, in wp_print_scripts order. The 'config' arg keeps working on both paths.
Nothing.
Two options, in order of preference.
1. Split it. Move the always-on part into its own small bundle enqueued the normal way, and leave the window's UI in the window's bundle. This is what the shell does for the Recycle Bin: the dock-tile state poller lives in the always-loaded shell bundle, and the 83 KB window bundle waits for a click.
2. Opt out. Declare preload_script and the bundle is enqueued at boot exactly as before:
openstation_register_window( 'my-plugin/inbox', array(
'title' => 'Inbox',
'template' => 'my_plugin_inbox_template',
'script' => 'my-plugin-inbox',
'preload_script' => true,
) );This costs every admin page the full weight of the bundle. It is the right call when the job genuinely cannot be split, and the wrong one when it can.
Declare it as a companion of that window rather than enqueueing it. Companions load in order immediately before the window's own script, so your subscriptions are in place before its render callback paints:
add_filter( 'openstation_my_wordpress_window_args', function ( $args ) {
$args['scripts'][] = 'my-plugin-explorer-extras';
return $args;
} );(That filter is WP Explorer's; every window that ships one exposes the same shape. A window you own takes 'scripts' => array( … ) directly.)
A stylesheet that only paints surfaces inside the window travels the same way, through 'styles' — injected on the window's first open, after the window's own style, so its equal-specificity overrides win by source order. Without it, the sheet has to be enqueued eagerly, which stamps it into every admin document (chromeless iframes included) where it can style nothing:
add_filter( 'openstation_my_wordpress_window_args', function ( $args ) {
$args['scripts'][] = 'my-plugin-explorer-extras';
$args['styles'][] = 'my-plugin-explorer-extras';
return $args;
} );The in-tree example is my-wordpress-woocommerce, which hooks WP Explorer's preview-extras / group-extras actions. It used to be enqueued on every admin page of every WooCommerce store; the bundle and its stylesheet now ride the window they extend.
Load it first:
await wp.os.loadWindowScript( 'desktop-mode-my-wordpress' );
await wp.os.myWordpress.trashEntity( 'posts', 42 );wp.os.myWordpress specifically does not need this — the shell ships an early stub that forwards through the same path for you, so existing calls keep working. The general helper is there for bundles that publish an API without one.
wp.os.debug.window( id ) reports the resolved handle, URL, and loadPath ('eager' | 'lazy' | 'unknown'). In DevTools, a deferred bundle appears in the network log at the moment its window opens rather than at page load.
-
architecture.md— the full delivery model. -
hooks-reference.md—script,scripts,styles,preload_script. -
javascript-reference.md—wp.os.loadWindowScript.
This wiki is generated from the docs/ directory — edits made here are overwritten by the next sync.
To change a page, open a pull request against docs/.
Guides
- Development guide
- Releasing openstation
- Agents security model
- API Index
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Native Desktop Host — Experimental
- Desktop themes
- Dock customization — two registries, one mental model
- The event-driven framework
- Files on the Desktop
- Folder sharing
- Getting Started
- Hooks Reference
- Icons
- JavaScript Reference
- The Living Tree — algorithm definition
- Mio
- Native Windows & Framework Interop
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration: window, wallpaper and widget bundles load on demand
- Migration — the navigation model
- Migration: a native window's tabs move to the window chrome
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- Custom arrange-menu action
- Open a child window its owner can't cover
- Style a specific admin page inside the iframe
- Code Blue — register your plugin's log file
- Open a file in the Code editor (deep-link from any window)
- Connect to a window — title-bar button + iframe pub/sub
- Content changes — live-refresh every window listing your type
- Custom window chrome (Experimental)
- Register a custom unfocused-window effect
- Example: render a data table
- Real file storage — react to uploads, gate policy, share from PHP
- React to a window being set free onto the real desktop
- Cross-window devtools — instrumentation primitives
- Add a dock item with a badge
- Decorate the dock without forking the renderer
- Replace the dock rail entirely
- Retune the Drafts widget's AI writing assistant
- Gate OpenStation by role
- Iframe-initiated window opens
- Build a feed reader without the bookkeeping
- Inject data into openStationConfig
- Render a list without losing clicks — renderKeyedList()
- Example: layout primitives (body → panel → row → col)
- Use <os-*> components from a plugin that ships as a zip
- Restyle and drive Mio
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- Add an action button to a WP Explorer preview pane
- Example: native Posts window
- Example: native window with tabs
- Native windows
- Customize note → post conversion
- Send a notification
- OAuth relay — connect to an external service
- OS-file drop
- <os-flyout> — window-scoped sliding card
- Plugins window — extras
- Track who's around — wp.os.presence
- Example: progress bar
- PWA install — surface your own button
- React to window events
- Example: extend the Trash
- Register a slash-command
- Register a desktop theme from a plugin
- Register a game
- Example: register a desktop icon (Jorvy)
- Register a wallpaper
- Register a widget
- Related entities — extend the title bar's "Related" menu
- The native-window render ctx
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Accept drops on your desktop icon
- Give a tile two icons, one per state
- Add a row to a window's ⋯ menu
- Example: window activity & the status ring
- Window controls
- Subscribe to window lifecycle events
- Window links — relate windows and restyle the ties (Experimental)
- Window loading state — spinner overlay & ready signal
- Show a banner at the top of a window
- Pulse a window's icon — Window.requestAttention()
- Register a custom window reveal
- Window slots
- Window themes
- Native window with bundle-bound config