Delaying wp_default_scripts() prevents enqueing

Description

wp_enqueue_script() only works on a handle that is already registered. That means that the delaying tactic in [19865] can break JavaScript enqueueing of default scripts (mostly by plugins, potentially by themes), as they no longer work until init has fired.

This is significantly mitigated by ​our change in 3.3 that caused some trouble, as most people fixed their code then, and more developers are watching for _deprecated and _doing_it_wrong notices. But, it still affects some poorly coded themes.

We can fix this by double-calling wp_default_scripts() — calling it immediately but then calling it again on init in case it fires too early.

The double-calling isn't new for us -- we do this when registering default taxonomies and post types as well.

Change History (16)

wp_enqueue_script() does both: $wp_scripts->add() and then $wp_scripts->enqueue(). When it's called too early, wondering if we could delay these two calls until the right time, i.e. the 'wp_enqueue_scripts' or 'admin_enqueue_scripts' actions.

Another way to fix this would be to have a "default order" for some of the core scripts like Prototype before jQuery, libraries first, etc. Been thinking to add this to WP_Scripts and possibly to WP_Styles (and get rid of wp_prototype_before_jquery()).

On WP_Scripts construction, it has wp_default_scripts() fire immediately and again on init, rather than only firing immediately (3.3) or only firing on init (3.4). By firing it immediately, this allows a default-registered handle to be enqueued prior to init. By firing it again on init, it makes sure that all localizations are properly translated.

Any localize() calls in script-loader.php are delayed until did_action('init'). This is necessary because calling localize() multiple times does result in duplicative JavaScript. This allows us to add scripts immediately but delay their localization until init.

did_action('init') is true the moment init fires (even before it completes), and the add_action( 'init' ) to re-fire wp_default_scripts() only works before the moment that init fires. So normally, WP_Scripts would only be instantiated once.

It provides coverage in load-scripts.php for did_action()'s use in script-loader.

This works in all of the following issues:

When a default handle is registered prior to init. For example, this in a plugin:

wp_enqueue_script( 'jquery-ui-droppable' );

When a default handle that has a localize() component is registered prior to init. For example, this in a plugin:

wp_enqueue_script( 'password-strength-meter' );

When a handle and source is registered prior to init. For example, this in a plugin:

When a handle and source that has a localize component is registered prior to init. Note that localize() won't re-run on init; so any translations *won't work*. We still need localize() to run as they may be passing other kinds of data to the script. For example, this in a plugin:

Wrapping all of these in add_action( 'admin_enqueue_scripts', function() { and } ); should similarly get you the same results. The only exception would be that someRandomL10n's 'Post' string would properly be translated when fired on admin_enqueue_scripts.

into another method? So add localise_on_init() or similar to WP_Scripts and call that from script-loader.

Maybe. I was mainly concerned with keeping the diff small and logic simple for 3.4.1. We discussed a few options in that IRC log.

The main issue about supporting these "bad" plugins is that they have no idea they are doing things wrong because everything works as normal. The only exception is it breaks JS strings in localized installs, which most developers will never notice.

For WP_Scripts to work before init was a flaw in our original API. We have been issuing _doing_it_wrong() since 3.3, and, along with the bugs in 3.3 (fixed in 3.3.1) and 3.4 (fixed in 3.4.1), most developers have indeed fixed their stuff. So I'd probably hesitate to do anything further here.