WordPress.org

Make WordPress Core

Opened 5 years ago

Last modified 5 months ago

#31281 new enhancement

Register JavaScript/Underscore templates using the WP Dependency API

Reported by: F J Kaiser Owned by:
Milestone: Priority: normal
Severity: normal Version:
Component: Script Loader Keywords: has-patch has-unit-tests
Focuses: Cc:
PR Number:

Description

It would be much easier to handle JavaScript templates when they could get registered using wp_register/enqueue_script(). This would allow tight binding between (Backbone) scripts and their respective templates. It would also allow to easily switch out single (core or plugin) templates to extend the UI with custom extensions in plugins. Further more it would have the benefit of more fine grained control of what template to load where and therefore a lower memory footprint.

For a brief look at the overall concept I'm proposing, please visit this Gist. This is pretty much what I'm currently using.

So instead of stuffing all templates as endless <script type=text/template> list in a single file, we can then split them up into single files (job for a later ticket - plugins can make use of it immediately). This also allows plugin developers to easily pull in core scripts and templates without modification, which should result in a more native and integrated UI and UX (when it comes to animations) without knowing every single bit of what is happening behind the scenes.

wp_enqueue_script(
        'template',
        plugin_dir_url( __FILE__ ).'templates/template.tmpl',
        [ 'underscore', 'backbone', 'jquery', ],
        @filemtime( plugin_dir_path( __FILE__ ).'templates/template.tmpl' ),
        true
);

In the added patch, there's also a filter named 'allowed_js_template_extensions' to add custom extensions aside from the default .tmpl extension which should ease the usage of third party scripts and templates who might use other extension names.

Note to other patchers: The patch makes use of the \DOMDocument and \DOMElement classes. Please do not try to switch this out for some Regex. The DOM classes are by far the fastest solution and much more reliable and readable than any Regex can ever be.

This tickets/patches overall goal is to bring WP JS template handling closer to how native JS applications make use of it.

Additional Benefits: This also allows us to run performance optimization like HTML minification on it and further compress the served files (Grunt tasks available). Another option would be to concatenate those scripts on a per screen basis.

Attachments (3)

0001-31281-patch-WP-Dependency-access-for-JS-templates.patch (2.2 KB) - added by F J Kaiser 5 years ago.
0002-31281-patch-WP-Dependency-access-for-JS-templates.patch (2.2 KB) - added by F J Kaiser 5 years ago.
Same as previous, but with respect to core code styling
31281.diff (4.2 KB) - added by georgestephanis 3 years ago.
New way of doing this, more in keeping with existing methods.

Download all attachments as: .zip

Change History (21)

#1 @Funkatronic
5 years ago

Possible duplicate of #31273.

Looking good. Including templates like this would be so much cleaner.

Last edited 5 years ago by Funkatronic (previous) (diff)

#2 @danielbachhuber
5 years ago

#31273 was marked as a duplicate.

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


5 years ago

#4 follow-up: @rmccue
5 years ago

The patch here needs to be in 5.2 syntax, so no \s in the class names please. Also, needs to follow WP bracing style. :)

Also, as I noted on #31273, I think it'd be more valuable to allow just adding arbitrary HTML to tags via WP_Dependencies::add_data. Alternatively, a different dependency chain should be used rather than attaching this on top of the WP_Scripts one.

I'm thinking something like...

wp_script_add_data( 'handle', '<script type="text/x-javascript-hbs" id="tmpl-xyz">{{ this.title }}</script>' );

It's then trivial to allow Backbone/Underscore-style templating, as well as anything else you want to achieve (meta tags, etc).

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


5 years ago

@F J Kaiser
5 years ago

Same as previous, but with respect to core code styling

#6 in reply to: ↑ 4 ; follow-up: @F J Kaiser
5 years ago

Replying to rmccue:

The patch here needs to be in 5.2 syntax, so no \s in the class names please. Also, needs to follow WP bracing style. :)

Done - thanks for the hint. I completely forgot about that. New patch also checks the edge case if there's actually an extension, if not it returns whatever was added.

Also, as I noted on #31273, I think it'd be more valuable to allow just adding arbitrary HTML to tags via WP_Dependencies::add_data. Alternatively, a different dependency chain should be used rather than attaching this on top of the WP_Scripts one.

It's just attached to a filter and is completely inline with the Dependency API. There's no "on top of the \WP_Scripts one". Goal was to treat script templates as what they are: Dependencies like other JS scripts or Stylesheets. That's exactly what happens.

I'm thinking something like...

wp_script_add_data( 'handle', '<script type="text/x-javascript-hbs" id="tmpl-xyz">{{ this.title }}</script>' );

It's then trivial to allow Backbone/Underscore-style templating, as well as anything else you want to achieve (meta tags, etc).

I don't really get what you are aiming for with that (especially the "meta tags" part). Above would force me to fetch the file contents and put them into a callback. That's pretty much what I wanted to avoid and why it happens in the callback. Also, please note that templates are tightest connected to scripts (views for example) and therefore need to reliably get included when the view is included. This puts a lot of burden off the shoulders of any developer. Register your view, Register your template and just enqueue your view whenever you need it - the rest is plain core WP Dependency API magic. Bonus: No one needs to learn anything new and can just jump on it.

#7 in reply to: ↑ 6 ; follow-up: @sanchothefat
5 years ago

Replying to F J Kaiser:

I'm thinking something like...

wp_script_add_data( 'handle', '<script type="text/x-javascript-hbs" id="tmpl-xyz">{{ this.title }}</script>' );

It's then trivial to allow Backbone/Underscore-style templating, as well as anything else you want to achieve (meta tags, etc).

I don't really get what you are aiming for with that (especially the "meta tags" part). Above would force me to fetch the file contents and put them into a callback.

I agree with Ryan that if you're enqueueing templates it makes sense to allow passing in a script tag directly as well as a URL as I think there's a use case for generated templates.

However I think Franz is right in that it's a still a script so wp_enqueue_script() makes sense - the templates will be dependencies of other scripts.

I did wonder about things like meta tags where you might want to have some open graph tags dependent on others being present etc... or for removing their output by other plugins but the current remove_action() functionality is probably good enough. To get some progress on this my view is that it should be kept to the case of scripts where order of loading is more important.

The same functionality could be offered for passing in a <style> tag to wp_enqueue_style() but again CSS dependencies aren't as much of an issue as they have to go in the <head> tag you can still use remove_action().

#8 in reply to: ↑ 7 @F J Kaiser
5 years ago

Replying to sanchothefat:

Replying to F J Kaiser:
I agree with Ryan that if you're enqueueing templates it makes sense to allow passing in a script tag directly as well as a URL as I think there's a use case for generated templates.

I did wonder about things like meta tags where you might want to have some open graph tags dependent on others being present etc... or for removing their output by other plugins but the current remove_action() functionality is probably good enough. To get some progress on this my view is that it should be kept to the case of scripts where order of loading is more important.

I guess I now (partly) understand what the idea behind that is. And I agree that the idea of passing in strings as template is not that stupid. At least one can do that with other JS frameworks as well and part of the idea of that patch is to bring WP closer to how JS frameworks handle templates. This should allow non-WP/JS devs to more quickly jump onto WP.

I will inspect that when I find some time. If someone can point me to some link that explains the open graph stuff, I'll happily read that and take it into consideration. Thanks in advance.

Last edited 5 years ago by F J Kaiser (previous) (diff)

#9 @ChriCo
5 years ago

Shouldn't we implement a similar (working) function for wp_add_inline_style() to add styles?

Currently it is not possible via wp_enqueue_script() to add inline Scripts. It's only possible by using the wp_head/footer -Action and adding it inline with a <script> -Tag. Maybe we should switch from unflexible 5 parameters to a list (array, or better: class) and adding a param for "is_inline" => true|false?

/**
 * ..snip..
 *
 * @param array $args = array(
		handle		=> String
		src		=> String 	 	
		deps		=> Array
		ver		=> String|Int|Boolean 	by default false..
		in_footer	=> Boolean 		by default false
		is_inline	=> Boolean 		by default false
		attributes	=> Array 		the <script>-Tag-Attributes
	);
 *
 * @return void
*/
function wp_enqueue_script( $args ) {
	
	if ( ! is_array( $args ) ) {
		// some _deprecated_argument()-message.

		$function_args = func_get_args();
		$args = _wp_convert_enqueue_or_register_args( $function_args );
	}

	// snip the init and wp-scripts check.

	if ( ! isset( $args[ 'handle' ] ) ) {
		// some _doing_it_wrong()-message.

		return;
	}

	$default_args = array(
		'deps'		=> array(),
		'ver'		=> false,
		'in_footer'	=> false,
		'is_inline'	=> false,
		'type'		=> '',
		'attributes'	=> array()
	);
	$args = wp_parse_args( $args, $default_args );

	// snip the rest.
}

/**
 * Converting the max. old 5 parameters from wp_[enqueue|register]_[script|style] to a single array list.
 *
 * @param array $old_args array
 *
 * @return array $args array
 */
function _wp_convert_enqueue_or_register_args( $old_args = array() ) {

	$args = array();

	if ( isset( $old_args[ 1 ] ) ) {
		$args[ 'handle' ] = (string)$old_args[ 0 ];
	}
	if ( isset( $old_args[ 1 ] ) ) {
		$args[ 'src' ] = (string)$old_args[ 1 ];
	}
	if ( isset( $old_args[ 2 ] ) ) {
		$args[ 'deps' ] = (string)$old_args[ 2 ];
	}
	if ( isset( $old_args[ 3 ] ) ) {
		$args[ 'ver' ] = (string)$old_args[ 3 ];
	}
	if ( isset( $function_args[ 4 ] ) ) {
		$args[ 'in_footer' ] = (bool)$old_args[ 4 ];
	}

	return $args;
}

With this "solution" we are more flexible. We can remove the not required type="javascript/text", add the missing "attributes"-Parameter to add some custom attributes to the <script>-Tag like data-foo="bar", id="baz" or async|defer.

This could also be done for wp_[enqueue|register]_style() which currently lacks of support for adding inline-styles to the header/footer without enqueue-ing a .css-File.

Some examples:

// e.G inline template
$script_args = array(
	'handle'	=> 'inline-template',
	'src'		=> '/path/to/template.tmpl',
	'deps'		=> array( 'underscore', 'backbone', 'jquery', 'models' ),
	'in_footer'	=> true,
	'is_inline'	=> true,
	'attributes'	=> array(
		'type' => 'text/template'
	),
);
wp_enqueue_script( $script_args );
// should output in footer:
// <script id="inline-template" type"text/template"> /* content of /path/to/template.tmpl */ </script>


// e.G. inline script
$script_args = array(
	'handle'	=> 'inline-js',
	'src'		=> '/path/to/inline.js',
	'in_footer'	=> true
	'is_inline'	=> true
);
wp_enqueue_script( $script_args );
// should output in footer:
// <script id="inline-js"> /* content of /path/to/inline.js */ </script>

// e.G. async script
$script_args = array(
	'handle'	=> 'async-js',
	'src'		=> '/path/to/async.js',
	'attributes'	=> array(
		'async' => 'async'
	)
);
wp_enqueue_script( $script_args );


// should output in <head>:
// <script id="async-js" async src="/path/to/async.js"></script>
Last edited 4 years ago by ChriCo (previous) (diff)

#10 @iseulde
5 years ago

  • Version trunk deleted

#11 @ninjamonk
4 years ago

I would vote for a simpler implementation which would just require a new function for called wp_enqueue_template_script which is a straight up copy of wp_enqueue_script but with the last parameter containing the type for the script.

$type = "text/x-handlebars-template";
wp_enqueue_template_script( $handle, $src, $deps, $ver, $in_footer, $type );

this way you could use any javascript template system that uses a custom script type like handlebars.

This ticket was mentioned in Slack in #feature-respimg by kaiser. View the logs.


4 years ago

#13 @nacin
4 years ago

  1. Does it make sense to have something like this in core, given the filesystem hit?
  1. I do not believe we can reliably use DOMDocument in core.

#14 @ChriCo
4 years ago

Heyho.

  1. I think this solution can provide some more benefits to the script-/style-management in WordPress. Think about the use case for "above the fold styles" or the inline script/styles for emoji's, or the new WordPress-Embed-Script which is inline too.
  1. The DOMDocument only works, if we delivery valid HTML and works with UTF-8 encoding. Hm...maybe you don't need the DOMDocument to create inline scripts - see wp_localize_script() which just echos the script tag.

--

Some hosting providers are not allowing file_get_contents() via http, so we have to replace this:

<?php
str_replace( get_home_url( ) . '/', ABSPATH, $src );

--

Another solution could be, to add a new function to load script_templates with some filters - something like get_script_template( ... );.

Last edited 4 years ago by ChriCo (previous) (diff)

@georgestephanis
3 years ago

New way of doing this, more in keeping with existing methods.

#15 @georgestephanis
3 years ago

  • Keywords has-patch has-unit-tests added

My VVV is currently down, so this will need someone to run the new test to confirm it's running -- but I just added 31281.diff -- I feel it's a more WordPress-y way of doing this.

In short, devs can either call wp_script_add_template( 'handle', 'id', 'template' ); or $wp_scripts->add_template() with the same arguments. The seconds argument gets auto-prefixed with tmpl- as per normal WordPress and wp.template() standards, and the generated template prints out with the script (or before, in the case of concatenations).

It feels much more similar to wp_add_inline_style() and the like in behavior.

Oh, and as an added bonus, wp_script_add_template() can be called multiple times, and it will just add additional templates to the script in question -- unlike ->add_data() and such, which will replace the prior content with the new.

Also, as it takes in a string, instead of a file path, we avoid either subsequent loads client-side and filesystem hits as per @nacin's concern above.

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


3 years ago

This ticket was mentioned in Slack in #core-images by swissspidy. View the logs.


3 years ago

This ticket was mentioned in Slack in #core-images by georgestephanis. View the logs.


3 years ago

Note: See TracTickets for help on using tickets.