WordPress.org

Make WordPress Core

Opened 3 years ago

Last modified 5 months ago

#22249 new feature request

Add ability to set or remove attributes on enqueued scripts and styles.

Reported by: ryanve Owned by:
Milestone: Future Release Priority: normal
Severity: normal Version:
Component: Script Loader Keywords: dev-feedback has-patch reporter-feedback 2nd-opinion needs-testing
Focuses: Cc:

Description

I think it should be easier to customize the loading of scripts and styles (easier to customize the markup generated by the script/style system). Proposed solutions:

Solution 1: Allow wp_enqueue_script, wp_enqueue_style, wp_register_script, wp_register_style to accept an array of attributes as the $src parameter. For example:

wp_enqueue_script( 'my-plugin', array(
    'src' => 'http://example.com/js/app.js'
    'defer' => ''
    'data-my-plugin' => 'custom data attr value'
), array('jquery'), null, true );

Solution 2: Add a filter before the markup is generated that allows devs to filter the attributes while they are in array format. For example:

add_filter('script_loader_attrs', function ($attrs, $handle) {
    unset ( $attrs['type'] );
    'my-plugin' === $handle and $attrs['data-my-plugin'] = 'plugin data';
    $attrs['src'] = remove_query_arg( $attrs['src'] );
    return $attrs;
}, 12, 2);

In class.wp-scripts.php it might look something like:

$attrs = (array) apply_filters('script_loader_attrs', $attrs, $handle);

and/or:

$attrs = (array) apply_filters("{$handle}_script_loader_attrs", $attrs );

I imagine that solution 2 would be easier to implement than 1, and 2 allows for themes/plugins to modify scripts/styles w/o re-registering resources.

The key feature of both solutions is the ability to modify the attrs while in array format. There are other ways that one could achieve the same results, but the array is by far the cleanest. Dirty alternatives include:

  • Use preg_replace() on the markup after it is generated (see #22245)
  • Use output buffers and PHP's DOMElement interface
  • Filter away the "print_scripts_array" and regenerate the markupmanually.)

Attachments (4)

22249.diff (4.1 KB) - added by jtsternberg 11 months ago.
New method, get_script_attributes, to concatenate an array of script attributes passed through a filter.
22249-2.diff (9.4 KB) - added by jtsternberg 6 months ago.
Modifications based on feedback from boonebgorges and jeremyfelt
22249-3.diff (21.2 KB) - added by camdensegal 6 months ago.
Added enqueue/register styles attribute argument.
22249-4.diff (21.2 KB) - added by alex-ye 6 months ago.

Download all attachments as: .zip

Change History (22)

comment:1 @alex-ye3 years ago

  • Cc nashwan.doaqan@… added

Yes It might be a good idea , I was needing something like this when I was using LessCSS sometimes you need to change "rel" value to "stylesheet/less" .

comment:2 @jtsternberg2 years ago

  • Cc justin@… added

This is also needed for some 3rd party scripts. Outbrain looks like:

<script type="text/javascript" async="async" src="http://widgets.outbrain.com/outbrain.js"></script>

comment:3 @bpetty2 years ago

Related: #19742

comment:4 @ryanve2 years ago

#23236 would make this easier.

comment:5 @raphaelvalerio2 years ago

This feature would also be useful for removing type='text/javascript', since it's redundant in HTML5. As far as I know, currently the only way of using a <script> element without the type attribute is to hardcode it into your theme.

Styles do have a filter hook you can use to alter the markup:

add_filters( 'style_loader_tag' )

(see wp-includes/class.wp-styles.php, line 83)

I haven't tried hooking into this filter, but I think the whole <link> element is a string, not an array, which could be improved upon. But at least it's hookable.

Scripts, on the other hand, have no filter to hook. Not only that, but the <script> element is printed to the site with a simple echo statement right in the function, so there's no variable to modify.

This happens in wp-includes/class.wp-scripts.php, in the do_item() function on line 125. I toyed around with adding in a filter. Although this doesn't correspond exactly to what you're asking, here's one solution for the script tag side of things:

Replace lines 122-125 of wp-includes/class.wp-scripts.php with:

$attrs = array( 'type' => 'text/javascript' );

$attrs = apply_filters( 'script_loader_attrs', $attrs );

$attrs_string = '';

if(!empty( $attrs ) ) {
	foreach ( $attrs as $key => $value ) {
		$attrs_string .= "$key='" . esc_attr( $value ) . "' ";
	}
	$attrs = ltrim( $attrs_string );
}

if ( $this->do_concat )
	$this->print_html .= "<script " . $attrs . " src='$src'></script>\n";
else
	echo "<script " . $attrs . "src='$src'></script>\n";

You can then use add_filter in your themes/plugins like so:

add_filter( 'script_loader_attrs', 'my_function' );

function my_function( $attrs ) {
   $attrs = array('async' => 'async', 'charset' => 'utf8') // whatever attributes you want

   // alternatively, eliminate type='text/javascript' by emptying $attrs:
   // $attrs = '';

   return $attrs;
}

The code above will always produce XHTML type attributes, e.g. async='async'. I'd need to add in a little code in the loop if the author wanted the more succinct async HTML5 version of the boolean.

For backward compatibility, the code will default back to including type='text/javascript' if no filter hooks in.

If people are interested in trying this solution, I could upload a diff file.

comment:6 @raphaelvalerio2 years ago

  • Cc raphaelvalerio added

comment:7 @Volker_E.2 years ago

  • Cc Volker_E. added

comment:8 @lkraav21 months ago

  • Cc leho@… added

comment:9 @nacin16 months ago

  • Component changed from General to Script Loader

comment:10 in reply to: ↑ description @jphase11 months ago

Replying to ryanve:

I think it should be easier to customize the loading of scripts and styles (easier to customize the markup generated by the script/style system). Proposed solutions:

Solution 1: Allow wp_enqueue_script, wp_enqueue_style, wp_register_script, wp_register_style to accept an array of attributes as the $src parameter. For example:

wp_enqueue_script( 'my-plugin', array(
    'src' => 'http://example.com/js/app.js'
    'defer' => ''
    'data-my-plugin' => 'custom data attr value'
), array('jquery'), null, true );

I think Solution 1 makes complete sense from a developer's standpoint. This solution could keep any previous filters intact and provide a simple type check on the second param to change the way this is rendered. I'd be happy to help submit a diff for this or #23236 if any help is needed.

Solution 2: Add a filter before the markup is generated that allows devs to filter the attributes while they are in array format. For example:

add_filter('script_loader_attrs', function ($attrs, $handle) {
    unset ( $attrs['type'] );
    'my-plugin' === $handle and $attrs['data-my-plugin'] = 'plugin data';
    $attrs['src'] = remove_query_arg( $attrs['src'] );
    return $attrs;
}, 12, 2);

In class.wp-scripts.php it might look something like:

$attrs = (array) apply_filters('script_loader_attrs', $attrs, $handle);

and/or:

$attrs = (array) apply_filters("{$handle}_script_loader_attrs", $attrs );

I imagine that solution 2 would be easier to implement than 1, and 2 allows for themes/plugins to modify scripts/styles w/o re-registering resources.

The key feature of both solutions is the ability to modify the attrs while in array format. There are other ways that one could achieve the same results, but the array is by far the cleanest. Dirty alternatives include:

  • Use preg_replace() on the markup after it is generated (see #22245)
  • Use output buffers and PHP's DOMElement interface
  • Filter away the "print_scripts_array" and regenerate the markupmanually.)

@jtsternberg11 months ago

New method, get_script_attributes, to concatenate an array of script attributes passed through a filter.

comment:11 @jtsternberg11 months ago

  • Keywords has-patch reporter-feedback 2nd-opinion needs-testing added

With 22249.diff, you would be able to modify/remove the default type="text/javascript" as well as add your own attributes (like async="async").

comment:12 @danielbachhuber7 months ago

I like the first option, although we could just as easily do both. The use case is something I run into regularly.

comment:13 follow-ups: @boonebgorges7 months ago

22249.diff is a pretty cool idea. A couple thoughts I had while looking the patch over:

  • There was a suggestion earlier about making wp_register_script() accept an array for $src. 22249.diff doesn't include this, but hides everything in a filter. By not making this accessible as a function argument, it buries the functionality a bit. It also suggests that changes to these attributes are generally something you'd want to do on a specific site or with a specific theme - acting on *someone else's* scripts - rather than in a plugin that registers its own scripts. I can imagine situations where the plugin/theme registering the script would want to pass some custom attributes, and it seems pretty hackish to require them to add a filter for that purpose. So I'd suggest adding array support. (I'm not 100% sure that it makes sense to do this by allowing $src to be an array - in which case the variable name and docs would certainly have to be changed - or if another param should be added to the function for the attributes.)
  • Related: 'src' is not being passed to the new filter, but is hardcoded into the <script> tag. I can imagine some benefits of putting 'src' into the $default_attributes array and allowing it to be filtered as well. You'd probably need some sanity checks after the filter to make sure that 'src' hasn't been removed from the array.
  • Could stylesheets use the same treatment?

comment:14 in reply to: ↑ 13 @jeremyfelt6 months ago

  • Milestone changed from Awaiting Review to Future Release

Big +1. All major browsers now support async, which would be very helpful. defer is likely less important because of in_footer, though still helpful. I'm sure there are other attributes I'm not aware of that could be included and result in more plugins using wp_enqueue_scripts() rather than something else.

I like the idea of $src being an array. It could also make sense to turn the $in_footer parameter into an array - maybe array( 'in_footer' => false, 'attributes' => array( 'type' => 'text/javascript' ) ). This may better imply the order of output. Including text/javascript as default may also be overkill - not sure.

Related #12009

@jtsternberg6 months ago

Modifications based on feedback from boonebgorges and jeremyfelt

comment:15 in reply to: ↑ 13 @jtsternberg6 months ago

[22249-2.diff](https://core.trac.wordpress.org/attachment/ticket/22249/22249-2.diff) Contains updates to:

  • Change $in_footer param for wp_register_script and wp_enqueue_script to an $args array where 'attributes' could be passed with an array of attribute key/value pairs. Also accepts 'in_footer' as a parameter and has a back-compat check for a passed non-array value.
  • Includes the src attribute/value, the type attribute/value, and any attributes passed in via the new $args param on wp_register_script and wp_enqueue_script in the array of attributes passed to the new script_loader_attributes filter.

If this passes muster, and all agree, I can apply the same treatment to the css side of things.

Last edited 6 months ago by jtsternberg (previous) (diff)

@camdensegal6 months ago

Added enqueue/register styles attribute argument.

comment:16 @camdensegal6 months ago

I've added the same functionality for wp_enqueue_style and wp_register_style. Though instead of having the attributes in a sub-array I have it as the main array because the argument being replaced ($media) is also an attribute.

@alex-ye6 months ago

comment:17 @alex-ye6 months ago

An update for @camdensegal patch..

  • Style media attribute is always a string.
  • Use the double-quotation strings only when necessary.

Still needs testing...

Last edited 6 months ago by alex-ye (previous) (diff)

comment:18 @Viper007Bond5 months ago

[30403] allows you to do this but it'd still be nice to be able to control this more easily than via a filter.

Note: See TracTickets for help on using tickets.