Make WordPress Core

Opened 2 years ago

Closed 3 months ago

Last modified 12 days ago

#54958 closed defect (bug) (invalid)

Inconsistent behaviour for wp_add_inline_script between block-based and standard themes

Reported by: rustaurius's profile Rustaurius Owned by:
Milestone: Priority: normal
Severity: normal Version:
Component: Script Loader Keywords:
Focuses: Cc:

Description

When using a non-block based theme, data can be passed from PHP to JS inside of a shortcode using wp_add_inline_script:

Main plugin file

add_action( 'wp_enqueue_scripts', 'plugin_register_assets' );
function plugin_register_assets() {
    wp_register_script( 'plugin-js-handle', PLUGIN_URL . '/assets/js/file.js', array( 'jquery' ), PLUGIN_VERSION, true );
}

Shortcode

class shortcodeClass {
public function render() {
    $this->enqueue_assets();

    // Output shortcode content
}

public function enqueue_assets() {
    $args = array(
        // array data here
    );

    wp_enqueue_script( 'plugin-js-handle' );
    
    wp_add_inline_script(
        'plugin-js-handle',
        'const plugin_php_data = ' . json_encode( $args ),
        'before'
    );
}
}

When using a block theme, the following JS error is generated:
Uncaught ReferenceError: plugin_php_data is not defined

Specifically, the JS file handle isn’t being evaluated as registered on line 288 of class.wp-dependencies.php, and so wp_add_inline_script is failing, but only when a block-base theme is in use (I tested with the Twenty Twenty-Two and Tove block themes, works with all of the default, etc. non-block-based themes).

Is this an intended change in behaviour?

Change History (10)

#1 @sanzeeb3
2 years ago

The wp_enqueue_script() and wp_add_inline_script() should not used from within the shortcode content.

The function should be called using the wp_enqueue_scripts action hook if you want to call it on the front-end of the site. Calling it outside of an action hook can lead to problems like #11526.

#2 @Rustaurius
2 years ago

Thanks for the reply.

Is the suggestion then to enqueue plugin scripts on every page load in that case, or is there a way to check whether a shortcode is included in the HTML that is being loaded more generally? I know that you can check for a shortcode using has_shortcode:

if ( has_shortcode( $post->post_content, 'shortcode' ) ) { 
   //enqueue 
}

But my understanding was that this would miss certain contexts (if the shortcode was used in a widget, archives, etc.), and loading on every page would slow page load times.

#3 @sanzeeb3
2 years ago

It looks like it's safe to use wp_enqueue_script() within the short code callback to load script only on pages with shortcode. Instead of wp_add_inline_script(), you can pass JS directly to page script with <script> tag and return it along with shortcode content.

#4 @Rustaurius
2 years ago

  • Resolution set to invalid
  • Status changed from new to closed

Great, thanks for clarifying!

#5 @desrosj
2 years ago

  • Component changed from General to Script Loader
  • Milestone Awaiting Review deleted

#6 @alanfuller
2 years ago

  • Resolution invalid deleted
  • Status changed from closed to reopened

The solution "Instead of wp_add_inline_script(), you can pass JS directly to page script with <script> tag and return it along with shortcode content." doesnot solve the issue of adding inline script 'before' the registered script.

Has anyone analysed why FSE themes break the existing behaviour?

From my testing the issue only occurs when you enqueue a script that has been previously registered.

It has been common practice for a long time to register a script outside the shortcode and enqueue it when needed. This now breaks wp_add_inline_script and is more troublesome when it is meant to be 'before'.

Fortunately there is a solution, to actually wp_register_script inside the shortcode function.
This is clearly a 'hack' but an explanation of why FSE breaks existing functiosn would be nice.

#7 @desrosj
2 years ago

  • Milestone set to Awaiting Review

Re-adding the Awaiting Review milestone.

#8 @luigipulcini
23 months ago

Replying to alanfuller:

Has anyone analysed why FSE themes break the existing behaviour?

We extensively analyzed the situation and it appears that block themes (or at least Twenty Twenty-Two) fire the wp_enqueue_scripts action only after the the_post action and, in turn, after the_content.

So, while plugins registering a script during wp_enqueue_scripts will find it already registered when any shortcode callback is executed in regular themes, the same is not true for block themes. For them, a shortcode callback will be executed before wp_enqueue_scripts, thus before a plugin had a chance to register the script.

In general, using wp_localize_script and wp_add_inline_script should always be done at the same time with the registration. Therefore, if you are registering the script inside wp_enqueue_scripts and, at the same time, adding the inline script you need, then you are free to enqueue the script during the shortcode callback by using wp_enqueue_script with the handle only. In fact, WordPress has a mechanism to automatically re-enqueue a script that was prematurely enqueued before the registration whenever the script gets finally registered. So, from that point of view, the change in block themes doesn't create any harm. But wp_localize_script and wp_add_inline_script must always be executed after the script is registered.

In those situations where a block theme is used and the javascript parameters are based on values calculated inside the shortcode callback, there is no solution other than registering (not just enqueuing) the script and add the inline script inside the shortcode callback itself, even if that happens before wp_enqueue_scripts. Since WordPress cannot really register a script twice, this second approach is perfectly viable and effective.

Version 1, edited 23 months ago by luigipulcini (previous) (next) (diff)

#9 @codespacing
3 months ago

  • Resolution set to invalid
  • Status changed from reopened to closed

The approach that worked for me and aligns with the recommended method of loading scripts in WordPress consists of checking if the theme is a "Full-site-editing (FSE) / block" theme or classic using the WP function wp_is_block_theme. Then, enqueue the scripts in the standard manner for classic themes or use the hook wp_enqueue_scripts for FSE/block themes. And of course this approach is applicable within shortcode callbacks. Here's an example:

<?php
class CustomShortcode {

    // Constructor
    public function __construct() {
        add_shortcode('custom_html', array($this, 'display_custom_html'));
        add_action('wp_enqueue_scripts', array($this, 'register_scripts')); //  Register scripts
        add_action('wp_enqueue_scripts', array($this, 'register_styles')); //  Register styles
    }
    
    static function this(){
        return self::$_this;
    }
    
    public function register_scripts(){
        wp_register_script('my-script-handle', plugin_dir_url( __FILE__ ) . 'script.js', array(), '1.0', true);
    }
    
    public function enqueue_scripts($inline_script_data){
        wp_enqueue_script('my-script-handle');
        wp_add_inline_script('my-script-handle', $inline_script_data);
    }
    
    public function register_styles(){
        wp_register_style('my-style-handle', plugin_dir_url( __FILE__ ) . 'style.js', array(), '1.0');
    }
    
    public function enqueue_styles($inline_style){
        wp_enqueue_style('my-style-handle');
        wp_add_inline_style('my-style-handle', $inline_style);
    }

    // Shortcode callback function
    public function display_custom_html($atts) {
        
        /**
         * Enqueue scripts & styles based on the type of the theme
         * Note: "wp_script_is()" serves as a fallback for no-theme platforms, which cannot be detected using "wp_is_block_theme()"! */
        
        $inline_script = 'You inline script goes here'; // Script!
        $inline_style = 'You inline style goes here'; // Style!
                
        if((wp_is_block_theme() || !wp_script_is('my-script-handle', 'registered')) || !wp_script_is('my-script-handle', 'registered')){
            add_action('wp_enqueue_scripts', function() use ($inline_script, $inline_style){
                $this->enqueue_scripts($inline_script);
                $this->enqueue_styles($inline_style);
            });
        }else{
            $this->enqueue_scripts($inline_script);     
            $this->enqueue_styles($inline_style);       
        }
        
        $html = '<div class="custom-html">Your shortcode HTML content goes here</div>';
        
        return $html;
        
    }
    
}

new CustomShortcode();
Last edited 12 days ago by codespacing (previous) (diff)

#10 @desrosj
12 days ago

  • Milestone Awaiting Review deleted
Note: See TracTickets for help on using tickets.