Make WordPress Core

Opened 3 weeks ago

Last modified 7 days ago

#60862 new defect (bug)

wp_localize_script() on login_enqueue_scripts hook change in behavior

Reported by: salcode's profile salcode Owned by:
Milestone: 6.5.3 Priority: normal
Severity: normal Version: 6.5
Component: Script Loader Keywords: needs-unit-tests has-patch
Focuses: Cc:

Description

I'm seeing this change in behavior going from 6.4.3 to 6.5-RC4.

When I add the following code snippet to a file in mu-plugins.

<?php

add_action(
  'login_enqueue_scripts',
  function() {
    wp_localize_script(
      'wp-util',
      'salcodeExample',
      [
        'answerToTheUltimateQuestionOfLifeTheUniverseAndEverything' => 42,
      ]
    );
  }
);

and then I go to /wp-login.php and View Source.

6.4.3 and below

I see the line

var salcodeExample = {"answerToTheUltimateQuestionOfLifeTheUniverseAndEverything":"42"};

inside the <script type="text/javascript" id="wp-util-js-extra"> block.

This is the expected behavior.

6.5-RC4

The <script type="text/javascript" id="wp-util-js-extra"> block is still included but the line we added via our code does NOT appear.

Notes:

  1. I've tested with both the wp-util and the user-profile hooks and get the same behavior.
  2. I'm only seeing this behavior when calling wp_localize_script() on the login_enqueue_scripts (specifically, I'm not seeing this behavior when using the wp_enqueue_scripts hook)
  3. This issue was originally identified in this GitHub issue https://github.com/salcode/wp-fast-login/issues/26

Change History (12)

This ticket was mentioned in PR #6334 on WordPress/wordpress-develop by @aslamdoctor.


3 weeks ago
#1

  • Keywords has-patch added

Global variable $wp_scripts is not defined at the moment when it is being used by wp_localize_scripts() hook on login page. So to define it properly, there is a function wp_scripts() which checks and creates a new object of WP_Scripts class if it is not defined.

Trac ticket: https://core.trac.wordpress.org/ticket/60862

#2 @jorbin
3 weeks ago

  • Milestone changed from Awaiting Review to 6.5

Using git bisect and the example code, I found that this was introduced in [57377].

Moving to 6.5 for visibility but this might be something that needs to be fixed in 6.5.1

#3 @swissspidy
3 weeks ago

If I learned something from [57286] / #58696 it's that block registration causes scripts to be initialized and localized very early, which can lead to some odd side effects (as that ticket demonstrates). So this could be a starting point for debugging.

The attached PR fixes the symptom but not the underlying bug. But we should definitely identify and address the root cause here if possible. Plus, add test coverage.

#4 @swissspidy
3 weeks ago

So it's definitely because of a change with the block registration. Previously, many blocks had editorScript and viewScript entries. That caused scripts to be registered very early on init, leading to $wp_scripts being defined early. In [57377] those were all reworked to use the Interactivity API instead, so by default no early script registration happens.

The result is simple: when login_enqueue_scripts fires, no scripts have been registered yet. That only happens implicitly with this wp_enqueue_script( 'user-profile' ); call here which happens right _after_ the login_header() call that fires login_enqueue_scripts: https://github.com/WordPress/wordpress-develop/blob/f1c098e77f67ae2469e519757c7c3829288da5d6/src/wp-login.php#L1494

In other words, the code used to work mostly by pure coincidence. On any other page it's actually very similar, $wp_scripts isn't really initialized in a deterministic way. For example on the homepage it usually happens in _wp_admin_bar_init().

That's also why I wasn't able to reproduce this issue at first. If some other plugin happens to register a script on the login page early on, then you won't notice any issues.

That also means this issue might not be too widespread.

It's just that by default wp_scripts() is now not called early on the login page anymore.

Long story short:

This bug actually existed since forever, it just so happens that early block registration was hiding this bug.

A change similar to the one in the PR would fix the issue. IMHO that can be done in 6.5.1 or even 6.6 though.

Longer term, initializing $wp_scripts in a more deterministic way would probably be better. On the other hand, wp_scripts() seems to be designed to be used like this, so yeah maybe that's not needed.

#5 @jorbin
2 weeks ago

  • Keywords needs-unit-tests added
  • Milestone changed from 6.5 to 6.5.1

Thanks @swissspidy!

While this might not have been something that was intended, it is a behavior change from 6.4 to 6.5 that I think is worth fixing, so I'm milestoning it for 6.5.1 but agree that it needs an automated test. Maybe An E2E to ensure this localization on login_enqueue_scripts works as expected.

As a workaround, calling wp_scripts(); ensures that the global is setup.

<?php

add_action(
  'login_enqueue_scripts',
  function() {
    wp_scripts();

    wp_localize_script(
      'wp-util',
      'salcodeExample',
      [
        'answerToTheUltimateQuestionOfLifeTheUniverseAndEverything' => 42,
      ]
    );
  }
);

This ticket was mentioned in Slack in #core by jorbin. View the logs.


12 days ago

#7 @GuerillaDesigns
9 days ago

I have a similar issue where wp_localize_script used to work in a block registration, but since WP 6.5 that localization isn't happening at time where the data is usable by the JS. Because I can easily get that data in another way I have just worked around it. But I'm curious if this is related or not.

function moneygauge_cta_block_init() {
    $script_handle = 'moneygauge-cta-block-editor';
    wp_register_script(
        $script_handle,
        plugins_url('build/index.js', __FILE__),
        array('wp-blocks', 'wp-editor'),
        filemtime(plugin_dir_path(__FILE__) . 'build/index.js')
    );

    // Assuming your image is located at <your-plugin-dir>/assets/handsup.jpg
    $image_url = plugins_url('assets/handsup.jpeg', __FILE__);

    wp_localize_script($script_handle, 'moneygaugeData', [
        'imageUrl' => $image_url, // This is the full URL to the image
    ]);

    register_block_type(__DIR__ . '/build', [
        'editor_script' => $script_handle,
    ]);
}
add_action('init', 'moneygauge_cta_block_init');

#8 @swissspidy
7 days ago

@GuerillaDesigns I don't think that's related.

If you pass editor_script to register_block_type, it will automatically call register_block_script_handle for it, which means it will call wp_register_script. But you are already registering the script yourself, so that will get overridden.

If you're seeing different behavior in 6.5, that might be because of [57590]. I recommend opening a new ticket for this. FYI @gziolo.

#9 @swissspidy
7 days ago

  • Component changed from Login and Registration to Script Loader

#10 @gziolo
7 days ago

If you're seeing different behavior in 6.5, that might be because of [57590]. I recommend opening a new ticket for this. FYI @gziolo.

There are no functional changes in the linked changeset that would impact registration. It's only a special case that allows the script handle to be provided in the asset file. So it might be something else.

If you pass editor_script to register_block_type, it will automatically call register_block_script_handle for it, which means it will call wp_register_script. But you are already registering the script yourself, so that will get overridden.

If the script handle gets passed then during the block registration it only gets linked to the block, it never get registrated again. The registration only happens when the path to the file gets provided in block.json.

#11 @davidbaumwald
7 days ago

  • Milestone changed from 6.5.1 to 6.5.2

Milestone renamed

#12 @jorbin
7 days ago

  • Milestone changed from 6.5.2 to 6.5.3
Note: See TracTickets for help on using tickets.