Make WordPress Core

Opened 3 years ago

Last modified 15 hours ago

#59444 new enhancement

Use HEREDOC/NOWDOC for embedded CSS/JS code in PHP

Reported by: westonruter Owned by:
Priority: normal Milestone: 7.2
Component: General Version: 6.4
Severity: normal Keywords:
Cc: Focuses: javascript, css, coding-standards

Description (last modified by westonruter)

As part of #4773 and #58775, many manually-constructed inline <script> and <style> tags were replaced with API calls to wp_print_inline_script_tag() and wp_add_inline_style(), respectively. While doing so comes with key runtime benefits, a downside is introduced in terms of the developer experience. In particular, when editing a PHP file that has an embedded <style> or <script> tag, IDEs will do syntax highlighting and other code intelligence features for the embedded language. These IDE features are particularly important since the JS/CSS code in question is not located in a .js or .css file, meaning it is not included in static analysis checks (e.g. JSHint). This means that the IDE is the first line of defense against developers accidentally making a typo or introducing a syntax error which, if it passes code review, would otherwise only get discovered at runtime in user testing. These IDE features are also important for developer productivity (e.g. autocompletion).

I understand PhpStorm to be the most popular IDE for WordPress core development, and it has a language injection feature. It involves using comments that precede the embedded language string, for example:

wp_print_inline_script_tag( /** @lang JavaScript */ "document.body.className = document.body.className.replace('no-js','js');" );

or

wp_add_inline_style( 
        'admin-bar',
        // language=CSS
        '@media print { #wpadminbar { display:none; } }' 
);

VSCode has an issue for Universal Language Injections, but it was closed for being too difficult. Apparently the scope was to go to the extent of detecting embedded languages even without the language injection comments that Jetbrains IDEs support. The feature was apparently relegated to the extension space (cf. Syntax Highlight Guide).

That being said, it does appear that VSCode does support method that PhpStorm supports: Inject a language inside a nowdoc/heredoc string. (I can't seem to find any VSCode documentation about this, but it seems to work locally.) This allows you to do:

wp_print_inline_script_tag( 
<<<'JS'
        document.body.className = document.body.className.replace('no-js','js');
JS
);

Nevertheless, there are also issues with the ergonomics of heredoc strings, especially prior to PHP 7.3: the closing delimiter must always start at the beginning of the line, and it cannot have any comma following it on the same line. So that means you cannot do the following in PHP<7.3:

$scripts->add_inline_script(
        'media-widgets', 
        <<<'JS'
                wp.mediaWidgets.init();
        JS,
        'after'
);

Since WordPress only now just bumped the minimum requirement to 7.0, this isn't yet an option.

I suggest that PhpStorm's syntax for language injection comments be adopted in WordPress core as a convention. Since PhpStorm is apparently the most popular IDE for WordPress core development, it will be useful for core contributors. For VSCode, an extension could potentially be written that adds support for the same syntax. Otherwise, we wait until PHP 7.3 is the minimum requirement and switch all existing JS/CSS string literals to heredocs.

Update: Now that PHP 7.4 is the minimum version supported by WordPress, we can move forward with using the flexible HEREDOC/NOWDOC syntax:

The closing marker for doc strings is no longer required to be followed by a semicolon or newline. Additionally the closing marker may be indented, in which case the indentation will be stripped from all lines in the doc string.

For delimiters, use JS for JavaScript code, and CSS for CSS code.

Try using NOWDOC whenever possible. See Plugin Check issue and comment. In some cases, it may make sense to continue to use wp_remove_surrounding_empty_script_tags().

This is closely related to #59446, where any manually-constructed <script> tags needs to be eliminated in core in favor of wp_print_inline_script_tag() for compatibility with CSP.

Previous conversations:

Change History (6)

This ticket was mentioned in Slack in #core-js by westonruter. View the logs.


3 years ago

#2 @dmsnell
3 years ago

One benefit of using the HEREDOC strings is that it should be a trivial update to indent them once the minimum supported version is 7.3, and they will work in both IDEs until then. That's where my vote lies, because I'd rather have syntax highlighting and IDE aids than avoid a dangling HEREDOC closer.

That being said I think this is good either way so I wouldn't want to block an update that has a positive impact. It would simply be a little more difficult to change later when embedding the comments inside the PHP strings.

There HEREDOC syntax has another benefit I personally like: it encourages normal formatting of JS inside PHP, using newlines properly, and aligning code. When a normal string is the example I feel like that puts pressure on people to fit everything in a single long line; if they do need multiple lines, then there are multiple ways they might approach that, whereas the HEREDOC conveys a single straightforward way.

#3 @westonruter
3 years ago

  • Milestone Awaiting ReviewFuture Release

#4 @westonruter
40 hours ago

  • Description modified (diff)
  • Milestone Future Release7.2
  • Summary Add language injection comments for embedded languages in PHPUse HEREDOC/NOWDOC for embedded CSS/JS code in PHP

Now that WP requires PHP 7.4, we can migrate to using the HEREDOC/NOWDOC syntax.

#5 @westonruter
39 hours ago

  • Description modified (diff)

#6 @westonruter
15 hours ago

In 63481:

Administration: Use wp_print_inline_script_tag() in more places.

Continuing the migration away from manually constructed SCRIPT markup, a subset of the admin's raw inline scripts now print through wp_print_inline_script_tag(): those in wp-admin/admin-header.php, those in wp-admin/includes/media.php, and those in iframe_header()/iframe_footer() in wp-admin/includes/template.php. For those screens this is what r56687 did for the frontend and the login screen, making wp_inline_script_attributes the single point at which a per-request nonce can be attached. This is one step toward an admin Content Security Policy opt-in rather than the whole of it.

The data these scripts carry is now serialized with wp_json_encode() using JSON_HEX_TAG | JSON_UNESCAPED_SLASHES instead of being assembled from esc_js() string fragments. This corrects the values that reach JavaScript: esc_js() passes the text through _wp_specialchars(), so ajaxurl, pagenow, typenow, adminpage, thousandsSeparator, and decimalPoint previously arrived carrying HTML entities in place of the literal characters (e.g. a thousands_sep of & was delivered as &amp;).

Inlined JS is also unminified and reformatted, with improvements to strict-mode compatibility. Static blocks use nowdoc heredocs so that editors syntax-check the JavaScript.

Developed in https://github.com/WordPress/wordpress-develop/pull/13319.
Follow-up to r56687, r60681, r60909, r60913.

Props thanhtinpk, westonruter.
See #59444, #59446, #63851.

Note: See TracTickets for help on using tickets.