Problem/Motivation

It looks like someone removed the ability to attach inline JS. No idea why, but I need this and I cannot move this to a library file.

Issues:

  • JS cannot attached with 'html_head' as the order is random.
  • The order of code is broken as inline JS need to be added after JS files and not before.
  • CDATA is not added automatically and could be theme specific
  • Inline JS code cannot moved to files in many cases as these are dynamic and may not automatically drupalSettings.

LogicException: You are not allowed to use js in #attached in drupal_process_attached() (line 1759 of drupal8\core\includes\common.inc).

Example code. See http://cgit.drupalcode.org/google_analytics/tree/google_analytics.module... for more:

    if ($config->get('track.adsense')) {
      // Custom tracking. Prepend before all other JavaScript.
      // @TODO: https://support.google.com/adsense/answer/98142
      // sounds like it could be appended to $script.
      $page['#attached']['js'][] = array(
        'data' => $googleanalytics_adsense_script,
        'group' => JS_LIBRARY-1,
        'type' => 'inline',
      );
    }

    $page['#attached']['js'][] = array(
      'data' => $script,
      'scope' => 'header',
      'type' => 'inline',
    );

Additionally Wim pointed indirectly to #2368797: Optimize ajaxPageState to keep Drupal 8 sites fast on high-latency networks, prevent CSS/JS aggregation from taking down sites and use HTTP GET for AJAX requests. The presentation Optimizing the critical rendering path in this case shows that the objections about inline JS are fairly invalid and the document proves that inline CSS/JS assets are highly critical feature and placing all stuff in external files is simply incorrect for page load speed and mobile first, too.

Proposed resolution

Respect dependency and load order for inline JS and utilize the libraries api for inline js.

Remaining tasks

  • Write tests
  • Refactor patch from #173 to be used for JS and not CSS
  • Document any api changes

API changes

Unknown

Comments

hass’s picture

Component: javascript » asset library system
hass’s picture

vijaycs85’s picture

Status: Active » Closed (works as designed)

It is Working as designed (WAD). #2382557: Change JS settings into a separate asset type has made this change. Check the change notice for more details.

hass’s picture

Status: Closed (works as designed) » Active

Nope. This is a design flaw.

I need to attach inline JS or Google Analytics and Piwik will not work. (400.000 installs)

vijaycs85’s picture

Right, I'm sorry. The change notice I reference is for settings, not for inline.

vijaycs85’s picture

hass’s picture

#2382533: Attach assets only via the asset library system has broken all my modules. It is no longer working what is documented on the links you posted.

nod_’s picture

Category: Bug report » Feature request
Status: Active » Needs review

This should work:


    $element['#attached']['html_head'][] = array(array(
      '#tag' => 'script',
      '#value' => $script,
    ), 'ga_scripts');

I know the _drupal_add_html_head function is deprecated but the feature will stick around, core needs it.

dawehner’s picture

Status: Needs review » Active

There is nothing to review here, sorry.

nod_’s picture

The solution to the support request? I don't feel like we need to add anything to core to fix the underlying issue (I don't think this should count as feature request).

If I look only at the title this is a "closed works as designed" issue.

hass’s picture

Category: Feature request » Bug report

Sorry, we need inline support for JS. No suxxx workarounds that break again. I seriously have no idea why you removed this must exist feature. The order is also important as I know. Inline must come after js files, but before settings. That is not possible with above code.

Feel free to change the title to something you like more, but add inline js support back, please.

catch’s picture

Category: Bug report » Support request
Priority: Critical » Normal
Status: Active » Fixed

nod_ answered this in #8.

Until that's been tried and proved not to work, please don't change the status around.

hass’s picture

Category: Support request » Bug report
Priority: Normal » Critical
Status: Fixed » Active

It is not working. The order of code is incorrect as html_head is on top and not after js files.

nod_’s picture

Inline must come after js files, but before settings.

That is not possible. Settings are the very first JS printed, before JS files.

I disagree that inline JS support is required. I'm not seeing any argument as to why we'd want to add it back. My argument in support for the removal is that inline JS is HTML markup, not assets and it should be treated as such: html_head, #markup or actually inlined in a template file. Supporting inline means we encourage or even support generating JS from PHP, out of reach from eslint and easy review. I'm entirely against that. The only feature I'd consider is the inline-file type. Outside of this, if you're generating JS from PHP you're on your own.

Looking on the outside you can see that wordpress doesn't really support inline JS either. On TYPO3 side it looks very crude as well. Both did rather well for themselves without this feature.

hass’s picture

If my inline code requires a js script this need to be loaded before. Therefore the order is important.

Only for the reason that you may not need inline js do not make this request invalid. Htm_head is added top most and js Files later. This will therefore not work.

There are modules that need to add inline js code based on php code. There is nothing wrong with this. These file-inline is totally different. No idea why we need this, but there may be reasons like generating from php. This may be not that mainstream, but it is required from time to time.

vijaycs85’s picture

StatusFileSize
new455.03 KB

I think there are two use cases in the issue summary.

  1. add inline JS just before any JS file (JS_LIBRARY-1) - I don't know how to do this.
  2. Add inline JS in head - which works fine as per #8 (attached screenshot)
hass’s picture

3. add inline after JS files.

nod_’s picture

I'd hook_preprocess_html it and add the inline js to $variables['scripts'].

catch’s picture

Category: Bug report » Support request
Priority: Critical » Normal

Still a support request.

hass’s picture

Title: Attaching inline JS crashes with LogicException » Regression: Design flaw change disallow attaching of inline JS

Now it is a bug.

hass’s picture

@Nod_: there may be bad alternatives, but we always had a clean API. We should keep this and do not implement such hacky workarounds that will cause more new troubles. Other modules cannot change the inline to footer if they like to speed up page loading as this code is no longer part of the attached JS. That is not flexible like the API was. One more fail.

nod_’s picture

Thanks to Wim js and CSS processing is fully pluggable. If you want clean, override https://api.drupal.org/api/drupal/core%21core.services.yml/service/asset... add back inline handling there. Then you can hook_js_alter in your stuff or somewhere else, just not in the #attached key (unless you make your own function that #attach will pick up).

All that can be done from contrib. If the 'inlinejs' module becomes wildly popular then we can talk about adding it back to core. Until then I don't see anything that needs changing.

vijaycs85’s picture

Thanks for the options @nod_. Just tried what you said in #22 and the result is here: https://www.drupal.org/project/inlinejs

hass’s picture

I will never depend Google Analytics on such a module. As you have broken all this inline stuff, can you document some examples, please? Keep in mind that I'm attaching the JS code in hook_page_attachments() and not in any other hook. This cannot changed to hook_preprocess_html().

And i missed one attach in http://cgit.drupalcode.org/google_analytics/tree/google_analytics.module... that require me to attach inline js in hook_item_list__search_results() and hook_item_list(). Also not changeable.

donquixote’s picture

Has the removal of explicit support for inline js been discussed somewhere?
As I see it, it is an side effect of #2382557: Change JS settings into a separate asset type, that was not properly discussed and documented.
A find-in-page for "inline" finds nothing in this issue or in the change notice (except hass' comment).

A lot of arguments can be made for or against inline js in the header. But this discussion did not happen.
I remember js libraries or APIs that tell you to "put this snippet into your html head".
I personally tend to put stuff into js files and not inline, but I know at least one person I work with who does it differently, and has valid arguments for doing so.

Having small page-specific snippets inline in head, instead of a file, can have several advantages:

  1. It can be built dynamically, e.g. using json dumps to fill variables.
    Yes, you can achieve this with settings instead, but maybe you don't want to. Especially, if you are coming from outside, then Drupal.settings.* could feel like a Drupalism, and an unnecessary complication.
  2. If aggregation is off, then these small files would unnecessarily increase the number of files to download.
  3. If aggregation is on, then these page-specific files cause differences between the aggregated js for page A and page B, meaning we now need two different aggregated js files instead of one.

Valid arguments to no longer support inline js in head would be:
- If the overall js community (if there is such a thing), or maybe a significant number of Drupal people, agrees that it's a bad idea, bad enough that it is *never* (or very rarely) legitimate.
- If explicit support for inline js would bloat Drupal core or cause technical issues.

None of these points have been made so far.
As I see it, explicit support for inline js would be quite cheap and easy. Whereas any of the alternatives proposed so far makes things more complex or fragile. There is always a tradeoff how much to support in core, but in this case I think core wins.

I'd hook_preprocess_html it and add the inline js to $variables['scripts'].

Seems ok, but
- order could still go wrong.
- you need to manually wrap it in

and the CDATA stuff, which I can never remember how to do right.
- does not work with #attached (although I don't know how much this is needed)

https://www.drupal.org/project/inlinejs

This is nice, but:
- has its own "exotic" API. (Not saying it's a bad API, but it would be more straightforward if supported in core)
- needs manual modification of sites/default/services.yml
- does not work with #attached
- replacing a core service with a custom implementation typically means that we no longer get modifications from upstream for this class.

--------

Otherwise, I like the changes from #2382557: Change JS settings into a separate asset type
Especially, I agree that there should be different array keys for things that require different formats and different logic.
Lumping it all together and then using 'type' to distinguish was just not a nice solution.

So why not simply

$element['#attached']['js_inline'][] = "alert('foo')";

(I don't really care about underscore vs camel)

jhodgdon’s picture

Status: Active » Closed (duplicate)

This discussion should be and has been happening on #2298551: Documentation updates adding/adjusting inline/external JS in themes for themes and #2301851: Make sure module developers have info on adding js/css to pages for modules.

Repeatedly opening up new issues is not really helping.

hass’s picture

Category: Support request » Bug report
Status: Closed (duplicate) » Active

None if the linked issues explain any if the problems I pointed out nor solve it only one. Please do not close the issue or we will not have a Google Analytics or Piwik for D8.

hass’s picture

Issue summary: View changes
hass’s picture

Issue summary: View changes
hass’s picture

Issue summary: View changes
hass’s picture

wim leers’s picture

Status: Active » Postponed (maintainer needs more info)
StatusFileSize
new3.63 KB

So let's address all raised concerns in one cohesive, comprehensive answer.

The concerns

I've collected all feedback regarding inline JS. There are basically 3 people who have voiced concerns or asked questions:

  1. hass, regarding Google Analytics, at #2382533-43: Attach assets only via the asset library system, -46, #2391029: Attaching inline JS no longer possible. Help get this back into core! and this issue.
  2. Jeff Burnz, regarding Live Reload and Typekit, at #2382533-44: Attach assets only via the asset library system
  3. Berdir, regarding AddThis (a sharing widget) and a licensed/hosted video player service, in IRC, with transcript below

What is inline JavaScript?

Let's answer them in order. But before I do that, let's take a step back and look at what inline JavaScript is:

  1. The main reason for being/preferring inline over being stored in a file is either because it's for 3rd party services, to embed their widgets (Twitter, Facebook …) or because you want to apply some performance-sensitive or time-sensitive page-level things (Google Analytics, on-site support via chat …). Almost
  2. 99% of inline JS is for integrating 3rd party services, and usually it doesn't need to be inline, but that just happens to be what's recommended by the 3rd party, because that's the easiest for them to support/document.
  3. 99% of inline JS doesn't have dependencies on other JS.
  4. In Drupal 7, it's JS that is embedded within PHP, stored as a hardcoded string, and therefore could not be linted/easily reviewed.
  5. In Drupal 7, it was treated as any other JS asset, which meant that the order had to be respected, so if one piece of inline JS was ordered between two JS files, those two JS files could not be concatenated, resulting in 2 HTTP requests instead of 1.
  6. In Drupal 7, the docs said: Inline must come after js files, but before settings. That is not possible with above code..

Concern 1: hass, regarding Google Analytics

hass has helpfully summarized the key problems he encountered in the issue summary (thank you!):

  1. JS cannot attached with 'html_head' as the order is random.
  2. The order of code is broken as inline JS need to be added after JS files and not before.
  3. CDATA is not added automatically and could be theme specific
  4. Inline JS code cannot moved to files in many cases as these are dynamic and may not automatically drupalSettings.

Let's address these point by point:

  1. All ['#attached']['html_head'] markup is inserted before the scripts, and even before the styles. See html.html.twig:
    <html>
      <head>
        {{ head }}
        <title>{{ head_title }}</title>
        {{ styles }}
        {{ scripts }}
      </head>
    

    It is in {{ head }} that you will find all the markup you attached in ['#attached']['html_head']. So the order is definitely not random. The order of individual 'html_head' attachments on the other hand, that is not possible to control. (And even then it's still deterministic, not random, but it's fair to consider it "random"). It is true that Drupal 8 doesn't allow you to let inline JS depend on other JS though, but more about that in the next point.

  2. The statement in the issue summary is less clear than the one hass made in comment #11:

    Inline must come after js files, but before settings. That is not possible with above code.

    This was never guaranteed. It might've worked that way in Drupal 7, but that was more by accident than anything else. Remember, the Drupal 7 docs explicitly said: Inline must come after js files, but before settings. That is not possible with above code.
    The only reason I can think of to support this statement is that inline JS might want to use jQuery (or Underscore, or …).
    But… not a single 3rd party that asks you to embed some inline JS will rely on jQuery (or any other JS for that matter). So this is a straw man, a reason that isn't one. It's entirely possible that some people wrote inline JS that depended on jQuery (and yes, I'm aware that the docs even explicitly mention this), but that doesn't mean it's correct.
    Moreover, if your JS depends on some other JS, that indicates a certain minimum level of complexity. In that case, you want your JS to be reviewable and lintable. That's impossible if it's inline. Therefore I think it's fair to require that JS that uses other JS (and hence has dependencies) must live in a file. Can you give a reason why it's not possible to put some inline JS in a file instead?

  3. CDATA is an XML concept and is for XHTML only. Drupal 8 ships with HTML5. The world has shifted to use HTML5. CDATA is a relic of the past.
  4. If there are so many cases, you won't have trouble formulating many examples. Please provide clear examples.
    (Also, in Drupal 7, JS settings appear after JS files, which means your inline JS wouldn't have been able to use JS settings anyway.)

vijaycs85 tried in #16 to summarize the required use cases, which hass completed in #17, together they listed:

  1. add inline JS just before any JS file (JS_LIBRARY-1) - I don't know how to do this.
  2. add inline JS in head - which works fine as per #8 (attached screenshot)
  3. add inline after JS files./li>

Use case 2 works and is explicitly supported and now also explicitly documented at https://www.drupal.org/node/2274843 (search that page for "html_head" to find it).

Use cases 1 and 3 were answered by nod_ in #18: use hook_preprocess_html() and modify $variables['scripts']. I agree with hass that this is incredibly ugly, and a terrible DX.
But the actual question is: why do you want/need use cases 1 and 3 anyway? Per the above explanation, these use cases are wrong/invalid!

Now, you may think that I'm ignoring reality in my writing. So instead of just writing this comment, I actually also wrote a patch that makes Google Analytics work just fine now that the ability to declare inline JS assets no longer exists.

It looks like the only reason hass has been so vocal is that he's attaching inline JS twice, and needs it to live in a certain order. Copied verbatim from his code:

if ($config->get('track.adsense')) {
 $page['#attached']['js'][] = array(
   'data' => $googleanalytics_adsense_script,
   'group' => JS_LIBRARY-1,
   'type' => 'inline',
 );
}

$page['#attached']['js'][] = array(
  'data' => $script,
  'scope' => 'header',
  'type' => 'inline',
);

So the first bit of inline JS only exists in some cases, the second bit always exists, but the first bit must always be loaded first. By simply concatenating the two, that problem is actually fixed:

if ($config->get('track.adsense')) {
  $script = $googleanalytics_adsense_script . $script;
}

Now there is no ordering problem left. So it seems we've been having this unfruitful discussion solely because hass refuses to make the above change. hass: please prove me wrong.

Then there is one more bit, that hass remarked in #24, but that he only noticed after the above complaints. He has a need to attach inline JS in hook_item_list__search_results() (to adjust the URL to a custom one that gets sent to Google Analytics if there are zero search results). That inline JS — in the way he designed it to work — *must* run before the other inline JS, otherwise Google Analytics will record the actual URL instead of the custom URL. But that's only what it looks like, in actuality, it could just as well run later, because Google Analytics is lazy-loaded anyway, so if a certain setting happens before or after, that doesn't matter!
On top of that, this preprocess function is actually using a global variable. So… we might as well use that global variable directly, which then allows us to calculate the custom URL on the server side. Hence the problem goes away completely.

The end result: simpler code, less code, and only a single piece of inline JS that needs to be loaded, as is to be expected.

Concern 2: Jeff Burnz, regarding Live Reload and TypeKit

Heres one of my cases that is, as I see it, very difficult to work around since this change.

I used inline JS for live reload, Typekit fonts and other things, this change forced me to use the asynchronous Typekit method which always gives a FOUT and is pretty dam hard to avoid even using their state classes. Inlining the JS is very fast method for Typekit, same goes for Google fonts, if you inline the link its super fast, but I cannot do that now (users select their font in settings), and I have to use their asynchronous JS method which gives a FOUT most of the time and there are no state classes at all.

I'm looking for ways around these issues, I understand the concerns in the issue and there maybe ways to get around the FOUT's etc, but this has made things much more difficult for front enders.

Let's begin with Live Reload. There, they ask you to embed a snippet like this:

<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')</script>

So you used to embed that using inline JS. You still can, if you put it in your template directly. But, what actually would have been better all along, especially considering that it needs to be loaded from the local host anyway, and considering that you put it in a module or a theme, is to load it as a JS file. Heck, even the module allows you to do that: http://cgit.drupalcode.org/livereload/tree/livereload.module. So this one is a non-issue for sure.

Next, TypeKit. Personally, I think the first problem is using TypeKit in the first place: it's a single point of failure that can take your entire site down, plus when loaded correctly (i.e. blocking, to prevent a FOUT), you're first waiting for JS to be downloaded, parsed and executed which then loads your fonts. This is absolutely terrible for front-end performance, because it prevents the browser from doing anything until TypeKit's JS has been downloaded and executed.
But let's talk about the matter at hand: how to handle TypeKit correctly when you no longer have the ability to do inline JS. Well, if you were doing it using inline JS (or even if you were using the recommended module, which seems to be https://www.drupal.org/project/fontyourface, and the TypeKit-specific bits at http://cgit.drupalcode.org/fontyourface/tree/modules/typekit_api/typekit...), your performance would have suffered terribly and you would be subject to FOUTs. With either method, the TypeKit JS appears after the CSS. But it should be loaded/executed before the CSS! See http://fvsch.com/code/typekit-url-cache/#blocking, which has an excellent explanation.

The only way to load TypeKit correctly is to put it in your html.html.twig (or to add a new variable in your html.html.twig, plus a hook_preprocess_html() to generate the markup you want to put there. That's okay, because a font is inherently tied to a theme anyway (a font is considered part of the styling and hence of the CSS — at least AFAIK).

So: also not a problem.

Concern 3: Berdir, regarding AddThis (a sharing widget) and a licensed/hosted video player service

The IRC transcript:

(11:49:06) berdir: WimLeers: ok. Here are a few example of inline js that I need to update, so you can have a look at that when you have time:
(11:49:16) berdir: dynamic external js: http://cgit.drupalcode.org/sharemessage/tree/src/Entity/Handler/ShareMessageViewBuilder.php?h=8.x-1.x#n76
(11:49:59) berdir: also, the drupal settings above that are I think wrong and should be inline js as well
(11:51:12) WimLeers: ok, thanks!
(11:51:23) WimLeers: berdir: such concrete examples are super helpful. If you have more, feel free to send them.
(11:51:48) berdir: WimLeers: more dynamic inline/external stuff inside a library alter: http://cgit.drupalcode.org/jw_player/tree/jw_player.module?h=8.x-1.x#n143
  1. http://cgit.drupalcode.org/sharemessage/tree/src/Entity/Handler/ShareMes... -> profile ID for external JS; this can be done in hook_library_info_alter(), easily. Completely unrelated to inline JS :)
  2. http://cgit.drupalcode.org/jw_player/tree/jw_player.module?h=8.x-1.x#n143, you actually meant line 150, because line 145 is the same case as above. Line 150 will require ['#attached']['html_head'] in the worst case, but since it requires downloading a library to be downloaded already anyway (and self-hosted), it could just as well be converted to use drupalSettings.

Conclusion

So far, not a single thing has come up that actually requires inline JS assets.

Finally, the handbook pages for adding CSS/JS to themes (https://www.drupal.org/node/2216195) and modules (https://www.drupal.org/node/2274843) have both been updated to contain the necessary information for dealing with inline JS. Because that was an actual problem.

hass’s picture

Correct order:

<html>
  <head>
    {{ head }}
    <title>{{ head_title }}</title>
    {{ styles }}
    {{ js_files }}
    {{ js_inline }}
    {{ drupalSettings }}
  </head>

The code is added in random order inside {{ head }} variable. We cannot add any weight to it as I know. We are also not able to weight above a module added code. I can only speculate here, but it may be required to integrate one module with another and than I need to add my code before/after any other module e.g. overriding a JS variable. There are situations that cannot solved with a global module weight.

99% of inline JS is for integrating 3rd party services, and usually it doesn't need to be inline, but that just happens to be what's recommended by the 3rd party, because that's the easiest for them to support/document.
99% of inline JS doesn't have dependencies on other JS.

That is incorrect. Google Analytics code need to be added to the page top, not footer. All our JS code is added to footer since a few months. The code GA adds is inline only. It cannot moved into an external file. Nearly every setting in GA settings page change the JS inline code added to a page. It is not "just the easiest way"...

CDATA is an XML concept and is for XHTML only. Drupal 8 ships with HTML5. The world has shifted to use HTML5. CDATA is a relic of the past.

We have a theme system as I know. We still allow people to change their themes and may run an older XHTML theme and do not require HTML5. In such a case they run into troubles as CDATA is missing and JS is hard coded in modules. If things change at D8 time and I try to implement HTML6 that may change again anything in how JS is surrounded nobody can solve issues as the tags are hardcoded in a module.

I'm not only asking here for GA and Piwik situations. By some brainstorming we can say that there arise issues that cannot solved the ways you told us today. Only for the reason that you found a way to move the pager in GA to a different position (something that I tried for sure in D6 and D7 and has not worked) may not solve all possible situations. The same situation can come back again. We may have not identified the issue yet. Always keep in mind that not many developers started porting or are following core close enough.

It is not wrong to have inline support and it is a lot easier to maintain code if I'm able to attach CSS and JS than running and markup stuff that may break in other themes.

donquixote’s picture

Thank you Wim for the detailed post!

I was thinking more of things like jQuery cycle (1 or 2), lightbox, fancybox..
Some of these need js code like this, which *does* depend on the library script and/or on jquery.js, and can have dynamic elements - e.g. the selectors and options could be dynamic. It is also usually page-specific.

$('#shuffle').cycle(options);

jQuery Cycle 2 has a declarative HTML syntax, so this is from Cycle 1. For Lightbox and Fancybox it was just harder to find a code snippet.

This stuff can be done with a file + Drupal.settings for dynamic options and selectors.
But some developers feel more comfortable with having it inline, and dynamically generating the js code.
This might be bad practice or not, I just think it is not our job to decide this.

One argument I usually hear is performance.
Even if the js file is cached, this does not apply on the first page visit. So if we download it as a separate file, it will increase the number of http requests.
On the other hand, if we aggregate it, this means our aggregated js will now be different on every page (or at least not the same site-wide). So this makes the aggregation less useful.

The other argument is that you want to let developers do what they are familiar with, for a faster and cheaper end product. This is a tradeoff, obviously.

donquixote’s picture

If the dynamic CSS/JS is built for each request, then you enter the truly advanced territory. This is hard, and for a good reason: per-request dynamic assets have to be built on every single request and therefore slow Drupal down.

This is the snippet from the doc page, which I think refers to cases like in #34.

I don't really see the performance argument being valid. The code is just as page-specific as other page elements, and as the settings. So it can be cached with the page. The performance impact is overrated, imo.

wim leers’s picture

#33:

Nothing you wrote supports your statement By some brainstorming we can say that there arise issues that cannot solved the ways you told us today.. You mostly answered besides the point. And you didn't answer my two questions:

Moreover, if your JS depends on some other JS, that indicates a certain minimum level of complexity. In that case, you want your JS to be reviewable and lintable. That's impossible if it's inline. Therefore I think it's fair to require that JS that uses other JS (and hence has dependencies) must live in a file. Can you give a reason why it's not possible to put some inline JS in a file instead?

hass: please prove me wrong.

Then, your statements:

That is incorrect. Google Analytics code need to be added to the page top, not footer.

I did not even mention the word "footer". No idea what you're saying here.

All our JS code is added to footer since a few months.

No, it's not.

The code GA adds is inline only. It cannot moved into an external file.

Sure, it can. The "cache locally" feature allows you to host it locally. Then you could have a drupal-google-analytics.js file that uses drupalSettings to set up window.GoogleAnalyticsObject and the window.ga alias (which is what Google Analytics uses today and will continue to use for years to come, since it's hardcoded in the snippet anyway), and which then loads ga.js just like the snippet does.

Nearly every setting in GA settings page change the JS inline code added to a page. It is not "just the easiest way"...

Indeed, you add more ga() calls in your inline JS. So what? It's easy to generate more inline JS. The only new requirement is that inline JS have no dependencies; if you generate all your inline JS in one go and then add it with $build['#attached']['html_head'], it works just fine.

We still allow people to change their themes and may run an older XHTML theme and do not require HTML5.

Another straw man. Because CDATA also works fine with HTML5. Your entire complaint is that it's no longer added by default and hence not overridable per theme. Well, here's the answer if you worry about XHTML themes: always use CDATA. It works fine in HTML5, it's just not necessary.

The same situation can come back again. We may have not identified the issue yet.

Only this is truthful. It's possible we're overlooking something. But thanks to your incessant complaining, I've now listed a big range of prior inline JS use cases that don't require inline JS. Which leaves only a very, very small chance that we'll find something that is truly no longer supported. We removed this for good reasons. I don't think we'll add it back unless we have a good reason.


#34:

This stuff can be done with a file + Drupal.settings for dynamic options and selectors.
But some developers feel more comfortable with having it inline, and dynamically generating the js code.
This might be bad practice or not, I just think it is not our job to decide this.

Well, you already answered it: it can be done. with a file plus drupalSettings. So why not do it?

People long preferred table-based layouts. We don't support that either. Writing such JS inline has *at least* equally bad consequences. The maintainability of a project using inline JS for that is a nightmare.

On the other hand, if we aggregate it, this means our aggregated js will now be different on every page (or at least not the same site-wide). So this makes the aggregation less useful.

No, because the files by definition remain unchanged. Any page-specific things live in drupalSettings, which is sent as (gasp!) inline JS, not as a file.
Worse, even, inline JS assets (as in D7) is usually the culprit of many more HTTP requests. I actually already wrote this in #32:

In Drupal 7, it was treated as any other JS asset, which meant that the order had to be respected, so if one piece of inline JS was ordered between two JS files, those two JS files could not be concatenated, resulting in 2 HTTP requests instead of 1.

The other argument is that you want to let developers do what they are familiar with, for a faster and cheaper end product. This is a tradeoff, obviously.

We learn from our mistakes, and help people prevent repeating the same mistakes. Inline JS was such a mistake. It's a maintainability nightmare and causes more HTTP requests.


#35:

I don't really see the performance argument being valid.

I kept the explanation in the handbook page to a minimum. But you have the answer in my reply to #34 already: it can cause more HTTP requests.

hass’s picture

The code GA adds is inline only. It cannot moved into an external file.

Sure, it can. The "cache locally" feature allows you to host it locally.

Nope. You misunderstood something. I'm not talking about the analytics.js file. I talk about the inline code. Inline code cannot moved into a file.

Then you could have a drupal-google-analytics.js file that uses drupalSettings to set up window.GoogleAnalyticsObject and the window.ga alias (which is what Google Analytics uses today and will continue to use for years to come, since it's hardcoded in the snippet anyway), and which then loads ga.js just like the snippet does.

We no longer loading a ga.js just as a note. We are using Univeral Analytics. First of all - I cannot implement anything Drupal like only for the reason that Drupal do not like inline any more. GA implements Google documented code. Other code is not supported by Google and everyone will ask - why I'm not using the original code from Google. All times in past I've done this - I had tons of request why it's different. And really there was no good reason to do it the other way. OT.

I'm feel like you are not aware how many options/settings/functions this module has and how many variables it set's and what can be set. This is totally unrealistic to be moved into an external file as there are millions of combinations possible, see https://developers.google.com/analytics/devguides/collection/analyticsjs/ what can be added into ga().

Moreover, if your JS depends on some other JS, that indicates a certain minimum level of complexity. In that case, you want your JS to be reviewable and lintable. That's impossible if it's inline. Therefore I think it's fair to require that JS that uses other JS (and hence has dependencies) must live in a file. Can you give a reason why it's not possible to put some inline JS in a file instead?

ga() may change on every page. I'm not talking about the simple standard tracker that is just an UA number. Please set up 20 metrics and 20 dimensions (note - you can have 200 each of them) with tokens and some of the custom settings and you will see what happens to the inline code. I do not think it is useful to create one JS file per visitor and for every single page or do you think that makes sense? This code must not aggregated nor minified and so on as it is tooo dynamic per user and page.

All our JS code is added to footer since a few months.

Sorry, it is the plan to do this soon.

Indeed, you add more ga() calls in your inline JS. So what? It's easy to generate more inline JS.

A few lines before you said I should move this into an external file and use DrupalSettings what is nevertheless not possible... that is confusing.

if you generate all your inline JS in one go and then add it with $build['#attached']['html_head'], it works just fine.

That is the problem. It cannot added in one go all times. You have seen the search example. Ok you found a workaround, but I cannot use this new solution for other contrib search modules. Some other contrib modules integrate with GA module and add code before or after mine and they depend on the GA module. Where code is added depends on logic and may require a weight functionality.

Another straw man. Because CDATA also works fine with HTML5. Your entire complaint is that it's no longer added by default and hence not overridable per theme. Well, here's the answer if you worry about XHTML themes: always use CDATA. It works fine in HTML5, it's just not necessary.

What should I do if it breaks in HTML6? How can a themer customize the surrounding JS script tags if this is hard coded in my module?

donquixote’s picture

@WimLeers:
Disclaimer: I am already on your team, I put all my js into files most of the time.
I am just not convinced yet that inline js is so bad that we need to discourage it with technical obstacles (or by removing explicit support for it).

No, because the files by definition remain unchanged. Any page-specific things live in drupalSettings, which is sent as (gasp!) inline JS, not as a file.

This is true. The contents of these files will not change between pages.
However, for js that is added with ['#attached']['js'], there will be a different combination of files on different areas of the site. Possibly even with different order, because stuff had a different order in the render array.
More conditional files -> more entropy. Or at least a higher chance for more entropy.

I kept the explanation in the handbook page to a minimum. But you have the answer in my reply to #34 already: it can cause more HTTP requests.

These additional HTTP requests were caused by a stupid implementation in Drupal 7, but are not a necessary consequence of inline js. If we would decide that all inline js comes after the js files, this would be a non-issue.

Jeff Burnz’s picture

Wim, I actually changed to doing this in the template several days ago, this was after much testing and thought on my particular use case over the past week or so.

Personally I prefer the async method using the webfont loader from Google and drupalSettings (since it does not block), but its to hard to control the FOUT in a generalised way, fine if you have one site and and do it specifically (the state classes it adds are very useful). I am not building "a" theme, I am building a theming system that underlies 10's of thousands of sites and themes, so this demands much higher attention to the long term problems associated with one approach or another.

I don't print all the code in the template, the TypeKit ID is a variable. The last thing I want is to be locked into a global Kit, you might only need a particular font on a few pages for example. I do basically the same thing with Google fonts now also.

This will work OK but it's not perfectly ideal, there are trade offs, such as long term maintenance issues that could arise. I have to be pretty aware of that, its not the end of the world, but something I would love to entirely avoid.

It would be good if we had more discussion, rather than some long post pointing out other methods etc, for the most part I am sure we are all aware of these things already, simply stating things are "not a problem" is a tad disingenuous, since you have not actually asked me any details around my use case, you just assumed a use case and even launched into some weird FUD about Typekit, sorry but I don't really see how that is relevant at all.

geerlingguy’s picture

Eh, so I'm asking the same thing, but for CSS. For example: http://cgit.drupalcode.org/honeypot/tree/honeypot.module#n128

      // Hide honeypot.
      '#attached' => [
        'css' => [
          '.' . $honeypot_class . ' { display: none !important; }' => ['type' => 'inline'],
        ],
      ],

That's in a form_alter, and while I could potentially generate a CSS file and use it, or use the hack outlined in #8 above, it just feels wrong/icky. I know of a lot of sites that do something similar—especially after we kept telling people to move from drupal_add_* to #attached... and now we're telling them to move from #attached to adding code inline to the page's <head>?

I'm working my way through reading this issue, but it seems a tough pill to swallow, since I didn't see anything wrong with the way we could attach CSS/JS before, and it felt so nice/natural. I assume this was something related to asset management? (There has to be a good reason this flexibility/convenience was removed, I'm just not seeing it).

geerlingguy’s picture

Category: Bug report » Support request
Status: Postponed (maintainer needs more info) » Active

Reopening after spending about 45 minutes trying to figure out a simple way to add a generated line of CSS to a page without having to build a file, add a library with a dummy file, implement an extra procedural hook, alter the dummy library and place my dynamically-built file in the library file's place (adding at least ~20 LoC and two new files to the module to replace one LoC... which was working a month ago).

Can we please get more examples? I think we may be underestimating the scope of disallowing dynamic inline CSS and JS via #attached. Even in the simplest case, it's quite a bit of effort to get this working, and I'd argue very strongly against the line in the docs:

In extremely rare and advanced cases, you may have the need to dynamically generate CSS and JS.

Many integrations do require some sort of inlining (e.g. TypeKit), and though these kinds of integrations are not necessarily ideal or perfect, they're a reality (and many sites are using the modules that do these things in D7), and I think we should at a minimum provide much more thorough examples of adding inline and dynamic JS/CSS besides "have a look at color.module".

AFAICT, there's not even a decent change record with D7 to D8 examples, and the only existing change record I found mentioning #attached in the title prior to larowlan assisting me in IRC was drupal_add_css(), drupal_add_js() and drupal_add_library() removed in favor of #attached, which doesn't even mention this issue, and seems completely irrelevant at this point.

geerlingguy’s picture

Title: Regression: Design flaw change disallow attaching of inline JS » Regression: Dynamic inline JS/CSS with #attached no longer possible
Related issues: +#2403753: Dynamic CSS in form results in LogicException "You are not allowed to use css in #attached"
geerlingguy’s picture

Title: Regression: Dynamic inline JS/CSS with #attached no longer possible » Inline JS/CSS with #attached not well supported

Nested arrays were killing me tonight, but I finally was able to use html_head instead of css for the key, and it worked:

    // Hide honeypot via dynamically-generated CSS.
    $form[$honeypot_element]['#attached']['html_head'][] = [[
      '#tag' => 'style',
      '#value' => '.' . $honeypot_class . ' { display: none !important; }',
    ], 'honeypot_css'];
hass’s picture

Category: Support request » Bug report
Priority: Normal » Critical
chx’s picture

I was thinking of MathJax when I saw this issue but http://cgit.drupalcode.org/mathjax/tree/mathjax.module#n84 the mathjax module doesn't inline JS so we are good (note how it used html_head to get settings in, however).

chx’s picture

Further thinking on this issue, the only time I can imagine inline JS is using a JS code generator written in PHP. That... is... insane. To say the least. The rest, put them in settings. Let me repeat this: if you have dynamic data that can go into settings. But how on earth did you get dynamic code that needs inlining?

And, even if you use generated JS (yay, TypeScript, I like TS) that's compiled into JS files and not generated dynamically inside your request.

catch’s picture

Category: Bug report » Support request
Priority: Critical » Normal
Status: Active » Fixed

@geerlinguy it looks like you've answered your own question. Before I saw that I thought of three ways to do this:

1. Use html head as you've done.

2. Since the class name depends on the configuration, write to a file when the config is saved.

3. Just add a class to the form element like element-hidden rather than the dynamic CSS (I'm assuming the use case is to have a different class name per site though, but that still leaves #1 and #2.

Since #1 clearly works (and the DX is not bad either), moving back to fixed.

hass’s picture

Title: Inline JS/CSS with #attached not well supported » Regression: Dynamic inline JS/CSS with #attached no longer possible
Category: Support request » Bug report
Priority: Normal » Critical
Status: Fixed » Active

Do not highjack this case, please. This is not support. This is a bug.

#1 html_head does not work if you need weight. This is no solution and does not work.

chx’s picture

Category: Bug report » Support request
Priority: Critical » Normal

hass, did you read #46? Could you please answer how did you arrive to dynamic code that you need to inline? Also please do not override the branch maintainer's decisions on priority. It might be critical to you but it's not critical to the project -- the latter is decided by the core committers.

hass’s picture

Category: Support request » Bug report

Drupal do not need Google Analytics module? Are you really sure?

If maitainers make wrong decissions or do not think about all possible use cases we need to correct them. Core is a framework. Just for the reason you do not need it in core does not mean nobody else need it. Throw the first stone if you are free of faults.

Again, html_head has no weight. This means inline JS is in troubles and may break in some conditions. And if there are any suxxx libs that require me to set a variable (not a drupal settings var) before the code I may not able to change this.

There is also zero feedback to #38 and #39.

hass’s picture

I can find more examples if needed.

chx’s picture

window[drupal.settings.foo] = drupal.settings.bar; in your JS?

hass’s picture

I'm talking against a wall. It is not my code. The google code from google server cannot altered, but requires the variable to be set before the code I'm adding.

chx’s picture

Let me elaborate then: you can write a small shim JS to do whatever needs to be done. MathJax sources the main JS from the MathJax CDN and uses http://cgit.drupalcode.org/mathjax/tree/mathjax.js a shim as well instead of inlining. I maintain (together with Wim) that dynamic, must-inline JS practically doesn't exist.

chx’s picture

Category: Bug report » Support request
Status: Active » Fixed

OK, one week and no more problems raised. Closing it.

hass’s picture

Category: Support request » Bug report
Status: Fixed » Active

My issues are still not solved. Why the heck are you closing this case?

Still no answer to #38 and #39 and #51.

chx’s picture

#51, put in drupal.ga.js:

window['ga-disable-UA-' + drupal.settings.gaDisableUA] = true;

The rest I can't make heads or tails of. If someone puts in a question I will answer.

Jeff Burnz’s picture

#39 is not really a question, it's just a discussion point. I never expected an answer. What i was doing is just a nice friendly way for non-technical peeps to add their TypeKit ID or Google Fonts to their theme. There are two pretty strait forward approaches here - use drupalSettings or do it in the template (print a variable). Either way works, except with Google Fonts and drupalSettings approach I had to use the Google font loader which is not an option in Drupal contrib because its not GPL compatible code , so I did it in the template, not the most DRY approach but it works and its fine for my project.

chx’s picture

Title: Regression: Dynamic inline JS/CSS with #attached no longer possible » Dynamic inline JS/CSS with #attached is longer possible
Category: Bug report » Support request

So #39 and #51 are down ; I re-read #38 and there's no material question there either that can be answered. Anyone else still have problems?

Also this is not a bug report but rather a support request: how do I write non-inline JS? In #57 I switched from English to JavaScript and glad to code more samples until everyone is satisfied.

hass’s picture

@Jeff: What template are you talking about? Theme? As module maintainer I cannot change theme templates.

@chx: #57. This does not work. The tag need to be added before the Drupal GA code, but it cannot as we have no weight parameter in html_head. Before pointing me again to anything you think that might work you should first read the Google Docs hot it need to be implemented and then try to code and verify it really works and than comment again.

Only two solutions are possible.

  • Add weight to html_head to allow every position in head.
  • Add js inline support back
Jeff Burnz’s picture

@hass, yes, I am working in a theme, my comments are more directed at other themers who might read this thread.

donquixote’s picture

@hass Do you ever go on IRC?

To summarize:

  • The Google Analytics snippet itself *must* be generated dynamically (and therefore preferably inline). A simple static (non-dynamic) js file + settings would not work, or be very complicated. Is this correct? Can you explain why?
    I think one reason is that you can have user-provided snippets..
    @chx: Here is the place in D7 where this code is generated: http://cgit.drupalcode.org/google_analytics/tree/googleanalytics.module?...
    I am not sure about the current status of this in D8, so I post the D7 version instead.
  • In D8, this inline js can only be added in html_head, which is before js files.
  • Other modules need to add stuff *before* this script.
    They could use html_head too, but there is no guarantee of the order.
    If it were not for the order problem, this could be done with js files + settings.

Alternatively, GA could provide its own API to add stuff in html_head before the GA script, but it seems much easier to do this in core.

Another solution could be js eval: eval(Drupal.settings.google_analytics.snippet);.
This does not seem very elegant, I am just posting it here for completeness.

donquixote’s picture

Btw, other modules that rely on inline js:
https://www.drupal.org/project/js_injector
https://www.drupal.org/project/injector (same idea as above)
https://www.drupal.org/project/cpn "Code per Node"

I remember something with Mailchimp, where I finally decided to let a client paste js from mailchimp into the js injector, instead of having to configure the mailchimp module. (this is a while ago, I don't remember every detail)

This work flow is useful for clients who want some control, but should not touch the filesystem. I am sure there are downsides, but in this case it was the right choice.

@chx: My main point in #38 is that the supposed problems with inline js are much overrated. We could re-add support for inline js in a way that does not suck, if we want to.

chx’s picture

@hass this is a support request for very good reasons. What you wanted to say in #60 is "I can only think of these two solutions, is there something better?". Yes, there is: assets within a library can have weights so you can have the small JS before the external Google script within a single library.

In the future #1945262: Introduce "before" and "after" for conditional ordering in library definitions will provide interlibrary dependencies.

Any other questions?

chx’s picture

Also, if you are another module that needs to work with and come before the external GA JS then https://api.drupal.org/api/drupal/core!modules!system!theme.api.php/func... it's easy to alter the library definition so.

hass’s picture

Category: Support request » Bug report

It is a bug case you only changed it to support

hass’s picture

I'm not aware how I can add inline JS as asset / library what makes it impossible to order. Have you read my comments above? Can you tell me how I make my inline JS a library without JS files?

chx’s picture

Category: Bug report » Support request

You do not need inline JS like here instead

  1. You write a library that adds the Google GA JS as an external asset.
  2. You write a custom JS that copies Drupal settings to GA settings.
  3. You add the the custom JS in the library so that it loads before the GA JS.

Please indicate having read this comment by quoting which list item you have a problem with and I will provide code samples for the problematic ones. If you continue insisting on inline JS I will get this thread locked as you refuse to be helped.

hass’s picture

Provide us code examples for everything you are talking about, please. I do not understand anything of what you are writing about.

Please stop trying to patronize me. If I tell you I'd like to add html_head or inline_js in specific order I'm not asking you how I need to bend Google code to a Drupal way. I'd only like to know how I and other modules can add their code like #51 to html_head with a weight above my library. Don't explain me I need to add it into a file, please.

Looking forward to your examples.

chx’s picture

I will not port your module, sorry. You need to put in some effort. And no, your port will not contain inline JS nor will it contain html head hacks, nor for that matter hook_init, hook_menu or any other of the billion things that got removed and/or changed significantly in D8. I very strongly suggest focusing on the end goals and researching what Drupal 8 offers towards achieving these goals. We can only help you walking on the road. If you insist on wandering somewhere else then it's impossible to help.

So, focusing on my three points plan:

  1. Look at core/tests/Drupal/Tests/Core/Asset/library_test_files/external.libraries.yml and core/modules/system/tests/modules/common_test/common_test.libraries.yml on how to add external assets to libraries (the latter has weights).
  2. Writing JS is not my forte. But looking at (window,document,"script"... in the D7 module for example, that surely will become (window,document,"script",settings.ga_url). I already pointed out that your JS might need to contain window['ga-disable-UA-' + drupal.settings.gaDisableUA] = true;. Everything you've done server side needs to move to client side with the only data in settings.
  3. We have already seen how to weight assets.

Please show your attempts in porting and where you got stuck and we will continue.

hass’s picture

Nobody asked you for porting the module. I have already done this for you. It is ready for release except the inline js that has been broken a few weeks ago.

  1. I guess I need to clarify one thing. I'm not adding an external asset. External assets are URLs like http://example.com/foo.js. See https://developers.google.com/analytics/devguides/collection/analyticsjs... how this dynamic Google inline code looks like, please. This code can change on every page and it is not possible to move this into a static file.
    <!-- Google Analytics -->
    <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
    ga('create', 'UA-XXXX-Y', 'auto');
    ga('send', 'pageview');
    </script>
    <!-- End Google Analytics -->
    
  2. Google requires that the window['ga-disable-UA-XXXXXX-Y'] = true; is added before the GA code from #1 is added. See https://developers.google.com/analytics/devguides/collection/gajs/#disable, please

    This window property must be set before the tracking code is called. This property must be set on each page where you want to disable Google Analytics tracking. If the property is not set or set to false then the tracking will work as usual.

  3. As I see no way how I can make #1 an external asset and also not how I can make this an internal I'm stuck with inline JS. This means I need to use html_head currently. This html_head does not have a weight. As other modules may add the code from #2 they are not able to make sure this code has a higher weight than #1 JS code and than the #1 JS code tracks the users where it should not.

Maybe you can take a look again into this and get a better idea what I'm talking about, please. Your comments sounds like you have no idea about the JS code of Google Analytics and how it works and how Google requires us to implement it.

chx’s picture

Ah I see that garbled JS loads the GA JS ; it's not Drupal. Oh well. That doesn't change much, really. This is the crux of the matter:

> As I see no way how I can make #1 an external asset

Well, why not? Ten seconds of Googling found http://stackoverflow.com/questions/1131079/is-it-possible-to-put-google-... this exact question. And then your external code can easily include what I repeatedly said: the code to copy Drupal settings into Google settings. I still don't see your problem; sorry.

> This code can change on every page

I am reasonably sure that most of it never changes. I mean, will it ever look like ([]+/H/)[1&11>>1]+(+[[]+(1-~1<<1)+(~1+1e1)+(1%11)+(1|1>>1|1) this? Well if you say it won't then why not? Because surely it has some parts that are not dynamic? So which parts are? Can we put them in Drupal settings or other, conditionally included JS files?

donquixote’s picture

Just for the record, after another look at the code, I have to agree with chx here.
http://cgit.drupalcode.org/google_analytics/tree/googleanalytics.module?...
Nothing here looks like it requires server-side code generation.

1. Un-minify the GA code.
2. Add some if / else based on Drupal.settings.
3. Pass prepared variables to Drupal.settings. Not the literal values from variable_get() (or rather \Drupal::config()), but prepared values that are relevant on client side.

But even if you stick with server-side code generation and do it in html_head, you could use sth like jQuery(document).ready() or whatever to postpone it, and then the window['ga-disable-UA-XXXXXX-Y'] = true; will all be ready when it executes.

-------

The exception is user-provided js code.

    // Add any custom code snippets if specified.
    $codesnippet_create = variable_get('googleanalytics_codesnippet_create', array());
    $codesnippet_before = variable_get('googleanalytics_codesnippet_before', '');
    $codesnippet_after = variable_get('googleanalytics_codesnippet_after', '');

You could use js eval(), but is this really desirable?
See also my comment #63 for other modules that let the user provide js code.

chx’s picture

Do NOT let the user enter JS snippets. We have been through with this over the course of a few releases with PHP code. Entering PHP code in the browser is oh-so-convenient at first sight but when it comes to maintenance it's a first class nightmare. No lint, no git, nothing. Yuck.

donquixote’s picture

It depends very much on the relationship you have with the client.
I usually hate it if I get to work on a site where the client has already added a bunch of js or php in the database, that I have to debug.

But, as said above, there was one example where the client could copy + paste a snippet from mailchimp, that was dynamically generated on the mailchimp side based on some settings done there. This allowed the client to do their thing without bothering me (and thus, having to pay me).

It also avoids to depend on a possibly buggy, outdated, feature-incomplete or possibly nonexistent Drupal module with a Drupal-specific UI.

When I started to refactor that, with the noble idea to put it in a js file, I quickly gave up because this provided snippet sucked. I wrote one support request to mailchimp, but then I gave up. (yes I could have just blindly pasted the entire thing, whatever)
This is all some time ago, and I don't remember all the details, but imo it worked out ok, and has not blown up (yet).

Another case was a chat service that you can add to any site by adding a js snippet.
I could talk the client out of it. But I will only do this if I have done my homework, and I am really convinced it's a bad idea. This does cost time.
Or I could paste the code into a js file in a custom module. But then I need to do it again if the client changes a setting in the service.

Instead, with js injector, the client feels empowered and can do their thing at their own risk, and get back to me if it doesn't work.
It can happen that the chat widget is never used, especially because the client won't be available to respond. But I rather let them make their own experience than spending valuable time to talk them out of it.

----------------

But even then maybe it does not have to be inline.
Maybe such code could instead be written to a server-writable js file.

donquixote’s picture

One example I just found is this: https://www.purechat.com/faq (3rd one opens a "getting started" video)
We could discuss all day if a chat is a good idea to have on a site, and how this one compares to similar services. But this is besides the point.

The essence is:

  • The service provides a snippet that depends on settings, so it needs to be updated whenever the settings change.
  • I (the developer / site builder) don't want to spend any (paid) time trying to understand how their system works, refactoring their code, or writing any custom code for it. This would defeat the entire low-cost low-barrier character of their service.
  • I don't want to spend any time tinkering with the settings. The clients are happy to do this on their own.
  • I don't want to spend too much time thinking about whether this is a good idea to add to the site. I would probably tell the client that this needs to be tested on different browsers and on mobile, and share some general thoughts and warnings about it. Maybe also a quick google search looking for other opinions. But this is going to be it.
  • The functionality is not essential to the site. If any debug situation comes up, I can temporarily disable this thing, and everything else will still work.

The solution could be:

  1. "Don't do it, it's a bad idea." -> I think it is not up to us to decide this.
  2. Re-add inline js support in core. (and then a contrib solution like js injector)
  3. Do it without inline js (e.g. with a generated js file).
  4. Add inline js support in contrib. (e.g. as part of js injector D8, or as a separate module as in #23).

Non-inline js tends to be architecturally superior, and easier to debug, read and maintain. Contrib developers should be encouraged to at least give it a try.
I think the GA js will look nicer after this conversion (except for the user-provided snippets, which I still consider an unresolved question).

The extra step of having to install a separate module (inlinejs), as in 4., may achieve exactly this goal.
But we need to explain that this is not to work around some arbitrary limitation of D8, but to actually encourage better quality code.
This means that even for D7 modules, using js files instead of generated inline js is a good idea.

On the other hand, a contrib module like inlinejs will likely need a more hacky or complex implementation than an equivalent solution in core.

hass’s picture

Well, why not? Ten seconds of Googling found http://stackoverflow.com/questions/1131079/is-it-possible-to-put-google-... this exact question.

This is complete bullshit. You really misunderstood something. This may looks like that it works, but this is for static tracking with extremely basic functionality only. This may be 1% of what the Google Analytics for Drupal module provides and allows. This is not what Marketing and SEO teams require today.

And then your external code can easily include what I repeatedly said: the code to copy Drupal settings into Google settings.

Again - no - it is not that simple.

I still don't see your problem; sorry.

That is because you have no idea about Google Analytics and it's functionality and flexibility. That is the main problem here.

I'm now going to share only a very few examples what I mean with dynamic tracker. It is only a extremely small snippet of what is possible and what happens. Maybe you get an idea what I mean with dynamic inline code. If you have read the links I already shared in #71 you may have seen the dynamic stuff. See https://developers.google.com/analytics/devguides/collection/analyticsjs/

I'm explicitly excluding the static part that is prefixed to every below example:

(function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,"script","//www.google-analytics.com/analytics.js","ga");

Example 1:

ga("create", "UA-1234-1", {"cookieDomain":"auto","siteSpeedSampleRate":10});
ga("set", "anonymizeIp", true);
ga("send", "pageview");

Example 2:

ga("create", "UA-1234-1", {"cookieDomain":"auto","siteSpeedSampleRate":10});
ga("set", "anonymizeIp", true);
ga("set", "page", (window.googleanalytics_search_results) ? "\/search\/node?search=test" : "\/search\/node?search=no-results%3Atest\u0026cat=no-results");
ga("send", "pageview");

Example 3:

ga("create", "UA-1234-1", {"cookieDomain":".example.com","siteSpeedSampleRate":10,"userId":"aAdjsxse5frd"});
ga("set", "anonymizeIp", true);
ga("send", "pageview");

Example 4:

ga("create", "UA-1234-1", {"cookieDomain":"auto","siteSpeedSampleRate":10,"allowLinker":true,"userId":"aAdjsxse5frd"});
ga("require", "linker");
ga("linker:autoLink", ["example.com","example.net","example.org"]);
ga("set", "anonymizeIp", true);
ga("send", "pageview");

Example 5:

ga("create", "UA-1234-1", {"cookieDomain":"auto","siteSpeedSampleRate":10,"userId":"aAdjsxse5frd"});
ga("require", "linkid", "linkid.js");
ga("set", "anonymizeIp", true);
ga("send", "pageview");
ga("send", "event", "Messages", "Status message", "This is an example node title.");

Example 6:

ga("create", "UA-1234-1", "auto");
ga("require", "linkid", "linkid.js");
ga("require", "displayfeatures");
ga("send", "pageview");

Example 7 (metrics and dimension can be 200 each and they are dynamic per page, values are token values):

ga("create", "UA-1234-1", {"cookieDomain":"auto"});
ga("set", "anonymizeIp", true);
ga("set", "dimension1", "ccc");
ga("set", "dimension2", "aaaa");
ga("set", "dimension4", "bbbb");
ga("set", "metric4", 0);
ga("send", "pageview");

Example 8 (debugging mode)

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics_debug.js','ga');
ga('create', 'UA-XXXX-Y', 'auto');
ga('send', 'pageview');

Please note that every page can have different ga() commands. This dynamic tracker cannot build with JS files or I need to create thousands of JS files on a site. That makes caching of JS totally useless and combining/compression of JS completely useless. This code requires preprocess = FALSE always, too.

And now I'm back to html_head that has no weight and disallow ordering the code snippets. I thought more on this and it is also possible that modules need to add their own inline code below the Google Analytics tracker (not before and not anywhere else; and also not in a blocking mode that may executes tooo late).

That means the source of webpage require to look like this:

<!-- google_analytics_tracking_blocker.module -->
<script>
window['ga-disable-UA-XXXX-Y'] = true;
</script>
<!-- End google_analytics_tracking_blocker.module -->
<!-- Start google_analytics.module -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<!-- End google_analytics.module -->
<!-- Start google_analytics_rules.module -->
<script>
ga("send", "event", "Actions", "Action", "Node has been saved.");
</script>
<!-- End google_analytics_rules.module -->

@donquixote: The tracker is a non-blocking async tracker. If I block it until document ready it is design wise wrong. Google spent a lot of time to make it non-blocking.

And finally another thing. Google has a validator if the tracking code is properly installed in a website. This one will fail if you move the code into an external file. And now search my queue how often people have problems with the validator... There are many cases just because they cannot read descriptions and enabled the local caching of analytics.js.

hass’s picture

Something we have not discussed yet is inline CSS. With inline JS it may be possible for some modules to workaround with settings and other with document ready or external assets, but with CSS i need to keep an order 100% intact or overridding is not working.

I used inline CSS for theme settings that changed the page width and some other details. If I'm not able to add them in correct order these settings have no effect.

Jeff Burnz’s picture

hass, what I did with inline CSS is declare the libraries to begin with and write the CSS to the file system. I have many debug settings that generate CSS and previously I just used inline CSS to inject this into the the right pages. For me it is a bit of a compromise to use static declared libraries so I need to look into this new dynamic library feature to see if I can use that instead.

wim leers’s picture

There is one other very simple and elegant solution: introduce hook_google_analytics_js(). The Google Analytics module invokes this hook. It allows weights to be specified. Then the Google Analytics module uses #attached']['html_head'], and bam, it works. IMHO this is superior to how it worked in D7 too, because 1) it keeps the GA inline JS together, 2) you semantically know what those bits of inline JS are for, 3) Google Analytics module is 100% in charge (it'd then be impossible for inline JS with ga() calls to appear, then other inline JS, then more ga() calls, and then "the actual" GA JS).

Even for custom/contrib module developers, this is better, because any inline JS that they defined before would no longer be in the same place as that for GA; that'd live in a separate hook. This improves DX and debuggability. And they'd still be equally encouraged to move their other inline JS — if any — into files.

dawehner’s picture

I asked myself why we can't just implement the same logic as we have in PHP in JS and pass along all the configuration and then use eval :P

hass’s picture

@Jeff: I cannot follow what you mean with dynamic library feature.

@Wim:

There is one other very simple and elegant solution: introduce hook_google_analytics_js(). The Google Analytics module invokes this hook. It allows weights to be specified.

1) I do not like to develop such an API just because core introduced a regression and is missing important API features. It is just a #weight on html_head that is missing or inline js. It is not worth to create an inflexible new hook/API that cannot handle all possible situations again. We have such an issue #231451: Add hook to alter data before sending it to browser. It's nearly 7 years old and not done and it cannot cover the ga-disable feature. Do you plan to work on this difficult one?

Also try order some inline CSS into the correct order. It's also not possible since your bad patch landed. Should every module and theme add it's own hook? Are you serious?

2) you semantically know what those bits of inline JS are for

No, I do not.

3) Google Analytics module is 100% in charge (it'd then be impossible for inline JS with ga() calls to appear, then other inline JS, then more ga() calls, and then "the actual" GA JS).

This means I need to integrate features in GA I do not like to integrate, clutter the module for all. There are things I do not like to spend my time on.

And they'd still be equally encouraged to move their other inline JS — if any — into files.

That seems to be the only reason. Force other to move inline code to static files, but ignoring that there are good reasons in some cases to do thinks the inline way. That over-complicates things a lot now.

@dawehner: What are you trying to say? I have no idea what this comment has to do with he problems raised here and the code that is added to a page. Drupal back to v4.6 has never used eval on GA JS code as I know.

I only proved where the missing inline js is already known to fail. Do we really need to wait for more examples? I do not think so and your suggested workarounds are awful and only confirm the faults. I do not understand why html_head has no weight. That looks like a design flaw, too. Missing inline js support is another fault and shows how inflexible Drupal 8 core is now.

donquixote’s picture

@hass (#77):

// @todo Settings should come from PHP.
settings = {
  library_tracker_url: '',
  tracklinkid: true,
  trackdoubleclick: true,
  domain_mode: 2,
  trackCrossDomains: true,
  anonymizeip: true,
  custom_vars: {},
  codesnippet_before: '',
  url_custom: '',
  messages_by_type: {},
  codesnippet_after: ''
};

(
  function(i,s,o,g,r,a,m) {
    i["GoogleAnalyticsObject"]=r;
    i[r] = i[r] || function() {
      (i[r].q=i[r].q||[]).push(arguments);
    };
    i[r].l = 1 * new Date();
    a = s.createElement(o);
    m = s.getElementsByTagName(o)[0];
    a.async=1;
    a.src=g;
    m.parentNode.insertBefore(a,m);
  }
) (window,document, "script", settings.library_tracker_url, 'ga');

ga("create", ' . drupal_json_encode($id) . ', ' . drupal_json_encode($create_only_fields) .');

// Add enhanced link attribution after 'create', but before 'pageview' send.
// @see https://support.google.com/analytics/answer/2558867
if (settings.tracklinkid) {
  ga("require", "linkid", "linkid.js");
}

// Add display features after 'create', but before 'pageview' send.
// @see https://support.google.com/analytics/answer/2444872
if (settings.trackdoubleclick) {
  ga("require", "displayfeatures");
}

// Domain tracking type.
if (settings.domain_mode == 2) {
  // Cross Domain tracking
  // https://developers.google.com/analytics/devguides/collection/upgrade/reference/gajs-analyticsjs#cross-domain
  ga("require", "linker");
  ga("linker:autoLink", settings.trackCrossDomains);
}

if (settings.anonymizeip) {
  ga("set", "anonymizeIp", true);
}

for (var key in settings.custom_vars) {
  ga('set', key, settings.custom_vars[key]);
}

if (settings.codesnippet_before) {
  eval(settings.codesnippet_before);
}
if (settings.url_custom) {
  ga("set", "page", settings.url_custom);
}

ga("send", "pageview");

for (var msg_type in settings.messages_by_type) {
  var messages = settings.messages_by_type[msg_type];
  for (var i_msg = 0; i_msg < messages.length; ++i_msg) {
    // @todo Translate 'Messages'.
    // @todo msg_type may need some preparation.
    ga('send', 'event', 't(Messages)', msg_type, messages[i_msg]);
  }
}

if (settings.codesnippet_after) {
  eval(settings.codesnippet_after);
}
mglaman’s picture

Coming from an outside opinion, I was really disappointed to see that the Google Analytics module didn't just work, and that embedding inline JS is going to be such a difficulty in Drupal 8.

This is going to be a killer when it comes to marketing agencies which utilize 3rd party widgets that need JavaScript snippets.

Yeah, write a library, do the three extra steps. Or just let people use #attached.

Pretty disappointed. Especially in the tone of the comments. Makes me wonder what the value in Drupal 8 is going to be if it's inflexible/too restricting.

Edit:

If you continue insisting on inline JS I will get this thread locked as you refuse to be helped.

Although disheartening, I can deal with this thought on the issue if there is documentation on how to solve this problem for contributors. Otherwise people, like myself, might just feel more alienated.

damienmckenna’s picture

Seeing as CPN was brought up, I figured I'd chime in as its current maintainer.

Code per Node exports everything to files and then uses drupal_add_js()/drupal_add_css() to load them on demand when published. However, during the node preview step it does use the 'inline' option rather than writing out a temporary file. Any suggestions for how to handle this use case?

Additionally, the module allows control of the weighting of the CSS and JS, so that they could be e.g. loaded before or after other JS loaded by the site's modules or theme. Would I need to drop this functionality, or is there a reasonable solution for handling it?

I'm going to ping some folks at work (Mediacurrent), drupalninja99 and others maintain or co-maintain a good many marketing-related modules that deal heavily with JS, they could add weight (ba-da-bum) to the issue.

hass’s picture

@All: *Thanks* Wim for pointing me indirectly to #2368797: Optimize ajaxPageState to keep Drupal 8 sites fast on high-latency networks, prevent CSS/JS aggregation from taking down sites and use HTTP GET for AJAX requests. The presentation https://docs.google.com/presentation/d/1Q44eWLI2qvZnmCF5oD2jCw-FFql9dYg3... in this case shows that the objections about inline JS are fairly invalid and the document proves that inline CSS/JS assets are highly critical feature and placing all stuff in external files is simply incorrect for page load speed and mobile first, too. Maybe you should read this presentation (again)?

@DamienMcKenna: The only possible workaround is currently attached html_head to add the inline code to the page, but you cannot order anything and this is why this case exists here, too.

hass’s picture

Issue tags: +frontend performance
damienmckenna’s picture

@hass: Thanks, that's what I was thinking.

So +1 for making this functionality available; even though it doesn't need to be used by core there is value in making it available for contrib.

damienmckenna’s picture

Category: Support request » Bug report

Putting this back to a bug report as there are several valid use cases where this needs to be possible.

danny englander’s picture

I've attempted to put inline JS in my Drupal 8 contrib theme without any luck. My use case is that I offer a UI theme setting for users to decide if they want to use LiveReload. This trypically would only be wanted on a local dev. In Drupal 7, this was as easy as pie in the theme's template.php file:

  // Live reload.
  if (theme_get_setting('gratis_livereload') == TRUE) {
    // livereload.js
    $livereload_js = 'http://localhost:35729/livereload.js';
    drupal_add_js($livereload_js, array(
      'scope' => 'footer',
    ));
  }

Now with Drupal 8, I attempt to set a var in my theme's .theme file within an preprocess_html function:

if (theme_get_setting('gratis_livereload') == TRUE) {
    $variables['livereload'] = '';
  }

Then in my theme's html.html.twig file:

{% if livereload %}
    <script src="http://localhost:35729/livereload.js"></script>
{% endif %}

No doubt, I am doing something wrong but perhaps not?

hass’s picture

@Wim: Can I get feedback on the mobile first issues raised in #87, please?

Jeff Burnz’s picture

#91 Danny, use the library system and #attached, so livereload.js is in a library declaration, add this as per normal in your themes themename.libraray.yml file then load in preprocess page or hook_page_attachments_alter(), e.g.:

# the library
livereload:
  version: 1.0.0
  js:
    scripts/livereload.js: {} # path to the file in your theme
// In preprocess page etc
if (theme_get_setting('gratis_livereload') === 1) {
  $variables['#attached']['library'][] = 'themeName/livereload';
}

The scope by default is going to be footer, you have to set this to header if you want it that way, in the library delcaration.

dom.’s picture

Hi,
I am dev of module Search Autocomplete currently porting it to D8.
In D7, I have autocompletion configurations created by admin through GUI that I pass to JS by inlineJS.
Converting this to D8, I now have Config Entity values that needs to be passed to my JS. What is the best way to do it therefore, regarding all what as been said ? Can someone just confirm that is this : https://www.drupal.org/node/304258#drupal-settings so I can go on with the proper way to do it ?
Thanks all

alexrayu’s picture

The newly imposed limitation seems to be limiting too much just for the sake of having each js file declared as a library. Seems to be a huge overkill. What if I don't want it to be a library, but some small narrow-purpose decorative js file, loading for just one very specific page, for one very specific reason? Why should I be forced into dragging the solution through the config system?

kevinquillen’s picture

How would you do the equivalent of this now... previously, I had a custom form element and one of its attributes was a #settings hash. When set, I injected settings into the Drupal JS settings scope like so:

array('selectize' => array('settings' => array($element['#id'] => json_encode($element['#settings'])))), array('type' => 'setting');

I cannot figure out how to do this in Drupal 8 now... Can we not attach Drupal settings or where is this stuff located now?

fabianx’s picture

#96: Please see https://www.drupal.org/node/2383115 for the change record.

kevinquillen’s picture

Thanks Fabian, that really helped.

JayeshSolanki’s picture

I had the same exception thrown while using 'js' in '#attached' for disqus module. The module uses inline js script 'window.close()' to automatically close the SSO login window of the site.
I was not sure if using a library file for a single line would be a good idea, so I instead used the 'html_head' as per #8. The issue link https://www.drupal.org/node/2482091
Please review to see if this should the right way of doing it. (ps: I didnt went through the whole discussion here)

frob’s picture

Title: Dynamic inline JS/CSS with #attached is longer possible » Dynamic inline JS/CSS with #attached is no longer possible

I am the maintainer for the google analytics lite module and I got around this by implementing hook_page_bottom. My use case is much simpler than the full google analytics module as all I am doing is allowing for an easy way for the ga code to be attached to a page.

I am working on bringing the js into a separate js file that is then assembled in client side and a dynamic library.

I have to say that this isn't ideal. I would much rather be assembling it at attaching it in the php space. However, I can think of no argument as to why I should be doing it that way other than it was easier before and because we have the use case of adding vendor script snippits it probably shouldn't be in the lint process anyway.

nitesh sethia’s picture

Drupal_add_js() and Drupal_add_css() are quite simple to write in Drupal 8.

We basically create a module_name.libraries.yml file having list of all the jss and css that we wish to use.

An example of which is


name_of_the_key: // This would basically work as a key for you.
  version: 1.x
  js:
    js/name_of_your_js_file.js: {}  // Name of your js file
  dependencies:  // Adding dependencies if you have any.
    - core/jquery
    - core/drupalSettings
  css:
    theme:
    css/name_of_the_css_file.css: {} // Name of your css file.

This would basically add all the files but would not attach these files to your page/form.

So in order to attach the js and css to your page/form, you simply replace

drupal_add_js()
drupal_add_css()

with

$form['#attached']['library'][] = 'name_of your_module/name_of_the_key';  

// Name of the key would be the key that you have defined in module_name.libraries.yml file.

So if your module uses different js and css file at different points in a forms/pages you could define n number of keys in your libraries.yml file and use it accordingly.

For more detail about it go to https://www.drupal.org/node/2274843 and http://drupal.stackexchange.com/questions/160270/how-do-we-use-drupal-ad... answer by Nitesh Sethia.

hass’s picture

@Nitesh Sethia: Before you comment on a case you should read the topic. Please remove your unrelated comment.

pwolanin’s picture

Actually, I think we should consider eliminating support for inline JS all together. It would make Drupal significantly more secure against XSS if we could enable content security policy headers to disallow inline JS by default in core.

https://www.owasp.org/index.php/Content_Security_Policy

hass’s picture

Have you read the article I linked to and wim pointed me to? Inline css/js is a critical feature for mobile first!

pwolanin’s picture

@hass - there are a lot of links in this thread - if there is a key one, please add it to the issue summary.

However, I did look at the one I think you are referring to, and it seemed to be some research focused on asset loading for single-page applications, not anything that seemed obviously relevant to me in terms of this discussion.

fabianx’s picture

Also unless there are 1000s of variations, I still think creating a JS file on disk (sites/default/files/js_inline) and hashing it by the contents of the previous inline JS would still be a possibility to have it be secure with the added advantage that it is then cached and can be aggregated.

Of course then need to think about necessary cache contexts, but that is a good thing given the state of things.

wim leers’s picture

#106: +1


I unpublished #101 because like hass said in #102, it is completely irrelevant and off-topic to this discussion. I also unpublished hass's #102 comment, because all it did was asking for #101 to remove his comment. So, effectively, I did that.

hass’s picture

@pwolanin: See #87, please.

@Fabianx:

Also unless there are 1000s of variations, I still think creating a JS file on disk (sites/default/files/js_inline) and hashing it by the contents of the previous inline JS would still be a possibility to have it be secure with the added advantage that it is then cached and can be aggregated.

Let's say we have 500.000 users on d.o. (I guess we have more). This means we need to have 500.000 users x per page different code - let's guess the best case 20. This means 500.000 * 20 = 10.000.000 files on disk only for Google Analytics. But it could be one per page... depends on tokens and features used. And someone could make it un-cachable by adding a token with current time. Than we are talking about 500.000 users * 255.000 number of pages or nodes on d.o = 127.500.000.000 variations on disk.

Let's lean back and re-think if we really want this? I say - NO.

hass’s picture

Issue summary: View changes

Added links to intro

hass’s picture

Issue summary: View changes

extended solutions

pwolanin’s picture

@hass "the document proves that inline CSS/JS assets are a highly critical feature..".

I'm sorry I'm really stupid, but I don't see anything in that that presentation that is even mentioning mobile first or inline JS, much less proving that inline JS assets are critical.

fabianx’s picture

CSP and Google Analytics from Google: https://developer.chrome.com/extensions/contentSecurityPolicy

https://developer.chrome.com/extensions/samples#event-tracking-with-goog...

Does not look like one needs inline JS according to Google.

pwolanin’s picture

@fabianx - I don't see too much that's useful there - that's more about extensions than using GA directly for a website? Also, their examples are using inline scripts for e.g. popup pages.

However, I would still expect that you could use a technique like the one we are discussing at #2510104: Convert drupalSettings from JavaScript to JSON, to allow for CSP in the future

Dump JSON data into the page and then use a script to parse it and invoke the GA functions.

fabianx’s picture

#113: Yes, for sure. I was just saying that chromium team also suggest to put the GA code into a JS file and not necessarily need to use it inline.

Of course they hard-code e.g. the GA ID, but that could live still in drupalSettings or custom div for sure.

frob’s picture

This is currently how the google analytics event tracking module works. JS is in a file, dynamic settings are loaded into the Drupal.settings object and then the data is parsed client side. I thought about doing it with inline js but it just seemed like an ugly solution.

With all of that said, I really dislike this "inline is dumb so we don't allow inline" talk. I prefer the system to give us enough rope to hang ourselves with and trust us not to hang ourselves. This conversation shouldn't be about if inline js is a good idea for a use-case. It should be about if it is a good idea to limit Drupal's functionality and forcing us to use hacks like what I said in #100

I agree, it is a good idea not to allow the user to input js via the ui
I agree, it is a good idea to avoid inline js
I do not agree it is a good idea to force Drupal's users to do hacky things to do something like adding inline js.

hass’s picture

Issue summary: View changes

@pwolanin: You are damn right. I'm very sorry I copied the wrong link. Here is the correct one: Optimizing the critical rendering path

@Fabian 112: I'm not getting your point what https://developer.chrome.com/extensions/contentSecurityPolicy has do to with Google Analytics. See https://developers.google.com/analytics/devguides/collection/analyticsjs/ if you are looking for one very basic inline code example. Please note that this is not what the GA module does if you configure a few features. You may like to take into the module code on d.o to get a idea.

Do you really think it is ok to have 127.500.000.000 files on disk? Please do not point me to DrupalSettings. This will not work as there are thousands of possible dynamic and optional ga() command combinations.

fabianx’s picture

#115: Well we think about supporting CSP: #2510104: Convert drupalSettings from JavaScript to JSON, to allow for CSP in the future

That would make using inline JS even more discouraged. (or plain forbidden for sites serious about security.)

That said I still think we could support loading inline JS via files instead to support cases like: "got a vendor snippet, put it in, works."

But that API would likely need some thought to avoid the 1 mio variations as hass pointed out.

fabianx’s picture

#116: Yes, that presentation is great.

While they speak about inlining, they also say that the best is to avoid JS in the head at all and load non-critical JS after the critical portion of the page has been rendered and while in this example inline-JS was used for the loader, that could also live in its own small file and most of the page has already been rendered then.

The important part here is to not block the rendering path, which JS _execution_ does, but if all that small loader.js does is to add a window.onload event, then that won't block the critical above-the-fold content display.

hass’s picture

@Fabian: 127.500.000.000 files not only 1 mio.

hass’s picture

GA JS code does not block. It run asnyc and defer.

We also need inline for CSS to render the page before the CSS files are downloaded to prevent a white page just because we are waiting for the download. This has a big section in this presentation.

pwolanin’s picture

@hass - ok, from the presentation it still seems CSS in the HEAD is much more important than JS?

damienmckenna’s picture

Out of interest, why is this discussion focused on "thou shalt not under any circumstances allow [x]" rather than "we shall not do [x] in core but allow APIs to do so in custom/contrib code"? The PHP module has been the bane of existence for many of us through the years, but wasn't dropped off a cliff and left to die, it was moved to contrib so that people could use it if they really knew what they were doing. Why can't APIs be left in place that allows contrib & custom code to do inline CSS or JS? Just provide the best out-of-the-box experience that we can, and leave APis open for us to do what we need, along with documentation explaining best practices and reasons not to do certain things.

donquixote’s picture

@DamianMcKenna (#122)
I would normally agree. But in this case, I think the implementation will be cleaner if this is done in core.
Or in other words, if core already provides *something* to allow contrib to add an inline js API in a non-hacky way, then the contrib module itself will have only little left to do, and will be rather symbolic. Maybe like the "Bad judgement" module.

webchick’s picture

Priority: Normal » Major

I legitimately do not understand why this issue is not being seen for what it is: a significant regression from D7, and something that everyone with a web development background will expect our framework to support, since inline CSS/JS is part of the HTML spec. Particularly given the apparent recommendations about inlining CSS/JS for better mobile performance.

Not going to dick around with status wars since I know catch already knocked this back from critical twice, but splitting the difference at major, at least.

nod_’s picture

We're not making inline js impossible, we're just not helping. Script tags can still be printed as any other HTML element, inline js can totally be written in templates. It's just that core has nothing working with #attached that would add inline JS, for the various reasons cited in the issue.

The number of website I've seen where a block was added to a page just to add some random inline JS (those websites had proper custom modules and all), makes me think people did not care much that there was an API for it before.

danny englander’s picture

... and then there are those of us who used inline JS properly by loading it via an API preprocess function back in D7. My use case is to implement livereload.js which is typically loaded inline but for a local site only. So I use a theme UI setting with an if condition in combination with a preprocess function to achieve this. I'd rather not have to do all that mess in a page template.

donquixote’s picture

We're not making inline js impossible, we're just not helping.

As said before (by me and others), all the creative alternative ways to add inline js and css are ugly, and they have limitations on controlling the order of inline styles / scripts.

I'm with webchick here.

hass’s picture

You guys have implemented great dependencies for js/css just as one example.

This is something i cannot do with add html head. But there are lot's of examples when I need to order inline js/css. Add html head has no weight, too. If css is not in correct order the layout of a page will clash! I see some examples that require ordering of js, too. They may not a prefered solution for sure, but I need this in some rare situations and it does NOT mean I have currently a workaround or I'm writing insecure inline js code what is one of the main arguments against inline js. It is possible to write secure inline js and there is also not a single example on the net how google analytics can be moved into an external file, too. I'm also sure google does not support this!

Additionally you try to remove drupalSettings in #2510104: Convert drupalSettings from JavaScript to JSON, to allow for CSP in the future from the page what will slow down page loading and disallow using google analytics as one example in head. That means I need to reinvent the wheel somehow what only suxxx.

alexrayu’s picture

You are not 'Not helping'. You are restricting. There was a legitimate and useful functionality in D7. The inline #attach. It got removed. Now, developers can not easily insert inline JS, like they used to. The reason given is that inline JS is bad. I value the opinion - but why should this philosophy be forced upon developers? You don't like inline JS - don't use it then. Why to you need to prevent everyone else from using this legitimate technique by removing it's support from render arrays?

Do not restrict other developers needlessly, please.

Kazanir’s picture

I have got to say I agree that removing this feature is a bad choice for all the reasons covered above. It shouldn't be a content management framework's job to force these choices upon developers.

Out of curiosity I went and checked out the top 10 US sites on Alexa. Every single one uses inline Javascript. All but one of them had inline JS in the <head> tag. (Craigslist was the sole exception.) That's not definitive, but I think it at least demonstrates that inline JS is a mainstream feature of the web.

I feel like core should provide an excellent and well-documented toolset -- especially for a feature which is in widespread use all over the web and where "best practices" (i.e. the mobile-loading vs. content security policy stuff) are in conflict.

pwolanin’s picture

Given that the security risk of inline CSS is much lower than inline JS, and there may be particular advantages to inline CSS for mobile devices, I can see an argument for not removing that.

For inline JS, I think we should not help people do that, but possibly add back an #attached API for adding inline JSON data that's not parsed as script (see the patch at #2510104: Convert drupalSettings from JavaScript to JSON, to allow for CSP in the future). It seems like all the per-page info can be output as data that's acted on by an external script, as opposing to directly inlining the script commands.

Or people can just dump those things in the drupal settings and we probably don't need an additional API or any support for inline JS.

fabianx’s picture

#131: I agree, but if we add it back we need to ensure that someone that adds inline CSS is also adding the necessary cacheability metadata, because this is the easiest way to shoot yourself in the foot easily. (if (condition) { add_dynamic_thing(); } )

I just got bitten myself by hook_javascript_settings_build() being cached (but _alter not) ... :-D

I think drupalSettings are enough for most use cases, but I can see a case for non-inline dynamic JS that is loaded from disk, but only variates on certain things, e.g. one javascript very specific per user role. So not dumping the hash, but instead having to supply the cacheable metadata and use that as the cache key.

hass’s picture

@Fabian: use metrics and dimensions with tokens that change on every page, please. Than you may get an idea what could happen.

We need both inline js and css, not just css.

webchick’s picture

Spoke about this issue at length with @Wim Leers, @effulgentsia, @pwolanin, etc. on a team call today. (This may not be captured 100% correct and if not, I apologize.)

We have a situation where developers are going to have pre-existing mental models, both because inline JS/CSS is part of the HTML spec, and also because nearly every (all?) ad code system out there has you copy/paste JS inline in order to get their stuff on the page. There are ways of doing this (ex: copy/paste into a block or Twig template) for site builders, but not so much for module developers. And because this was easy (or at least possible) for module developers in D7, there are developers in this thread who are (understandably) upset.

However, the concern of Wim, Peter, etc. is that if you (re-)add an API to doing inline JS for module developers, you will kill various other benefits that we've worked very hard on this release, such as "fast by default" page caching and improved security. They are also frustrated because they feel like they have tried very hard to provide solutions and those solutions are being disregarded or talked past. (There does indeed seem to be a lot of talking past each other I've noticed on this issue; we should strive to remove emotion out of our comments and stick to the facts because otherwise it makes it very difficult for us to hear each other.)

So as a starting point, I think we need really clear docs on how and why you need to write JS in Drupal 8 in the various use cases that are supported by the HTML spec. https://www.drupal.org/node/2274843 is the documentation with how to add JS/CSS in Drupal 8. Those docs are way over my head, but they do seem to cover all of the various use cases outlined here. Folks who do front-end development, can you please review the docs at and see where they're unclear, and/or edit so it contains the info/use cases you expect? (If you don't know an answer to a particular use case that's fine, put @todo and we can get more clarity here in comments and fill it in later.)

In particular, Attaching configurable JavaScript and the Inline JavaScript section seem to explain what you need to do with various use cases in this thread, and these options were also talked about at length in Wim's comment at #32.

So:

1) Read the existing docs
2) Edit them for improved clarity
3) Add headings for things that they don't explain yet
4) Ask questions on how to fill those in.

And above all, let's please be respectful and considerate in our interactions with one another here. We've all got the same goal: to make Drupal better. And the anger/resentment being expressed is masking the valid points that people have made here.

droplet’s picture

Everyone think #markup is the new JS INLINE API:) No changes at all. Just less powerful. No API === No one do it is only a dream.
I'm also a WordPress developer. WP has no JS inline API but inline JS is everywhere :) This is real life. We do it via #markup like function or code in theme layer directly.

At worse cases, module developers who don't know JS well, they may put all code in DOMContentLoaded / load events or any ugly way or extra coding. (this is more than copy and paste to a file)
( In fact, it may not happen because we can't do the job, we won't release as module. )

Result: this is "SLOW by default". (Sites use modules, I can't apply it to CORE ONLY)

Why not think in other side ? Let's say use inline JS API to help to remove inline JS:
https://www.drupal.org/node/2510104#comment-10064892

Also, If you have ever use PageSpeed by Google, you won't think cacheable JS/CSS is always "fast by default".

"improved security", this is only truth, no doubts. Just 2 remind:
- enable CSP do not fully protect XSS
- CSP 2.x going to add a workaround for inline JS.

hass’s picture

@webchick: None if the issues raised here are covered by current documentation or documented in https://www.drupal.org/node/2274843. The issues are real, but have always been neglegted as not the Drupal way or any other false arguments without the full picture. You really need to read Optimizing the critical rendering path first. Pay attention to mobile first, white pages not showing the page before JS is complete and so on.

The issues we are facing here are as follows:

  1. We can only add inline JS with 'html_head'.
  2. We can only add inline CSS with 'html_head'.
  3. We cannot order JS in head.
  4. We cannot order CSS in head.
  5. We cannot depend 'html_head' content on drupalSetttings as this 'html_head' is not a library.
  6. We cannot solve all mobile first issues.
  7. There are no known workarounds/solutions, also not in https://www.drupal.org/node/2274843.

What are the examples of problems where I cannot migrate to drupalSettings and external files?

  • In worst case we add inline CSS / JS code to head and the required drupalSetttings are missing on the page as they have no dependency to any other file and are not added to the page than.
  • Module A cannot override JS/CSS code from module B as it cannot add JS/CSS with weight and html_head adds the code in random order to head.
  • We cannot really use drupalSettings to populate CSS in a page with configurable settings without negative side effects like noted in #2274843. The reasons are simple. JS code is too late. It is loaded in the footer and only runs after the combined JS file has been downloaded. This means a ~250-500kb delay (also on mobile!) before the page completes and I only see a white screen as noted in Optimizing the critical rendering path. As noted in the presentation Wim shared - this is not mobile first. For some mobile first issues this need to run independed from external JS code. I could theoretically add inline CSS with html_head, but as noted several times, not with a weigth and therefore in random order only.
  • Google has documented a JS tracking code snippet on their pages. I need to implement it this way. Any other way like external files is not supported and NOT documented. It looks like Google also ignores CSP in this case or they are aware of it, but have no workaround or they waiting for CSP 2.x. I don't know.
  • These documented JS code looks simple at first sight, but it is NOT. The reason is nearly no Drupal site will run this simple example code. The issue is that the code does NOT look like this in most cases:
    ga('create', 'UA-XXXX-Y', 'auto');
    ga('send', 'pageview');
    

    It can have thousands of other settings and commands and I cannot use drupalSettings for this (what was another false argument). Just one example (the values can be tokens) and the code can be extended by other modules:

    ga('create', 'UA-XXXX-Y', { 'userId': '[sha2 hash of the current userid]' });
    ga('send', 'pageview', { 'dimension15':  'My Custom Dimension'});
    ga('send', 'event', 'category', 'action', { 'metric18': 8000 });
    ga('send', 'event', 'category', 'action', { 'metric19': 24.99 });
    ga('send', 'pageview');
    

    or :

    ga('create', 'UA-XXXX-Y', 'auto', {'name': 'myTracker'});
    ga('myTracker.require', 'ecommerce');
    var transaction = {
      'id': '1234',                    // Transaction ID.
      'affiliation': 'Acme Clothing',  // Affiliation or store name.
      'revenue': '11.99',              // Grand Total.
      'shipping': '5' ,                // Shipping.
      'tax': '1.29'                    // Tax.
    };
    ga('myTracker.ecommerce:addTransaction', transaction);
    ga('myTracker.ecommerce:send');
    

    This code is NOT made for external JS files!

  • There are situations where JS code need to be added before the tracking code. e.g. a hook has a value I need to provide to the tracking snippet. In this case I need to add it before the JS tracking code snippet, but I cannot as html_head has no weight and I cannot define dependencies on the tracking code snippet.
  • I have been told to move GA to external files. This is not possible as it is not supported very first, but it is also not possible as GA is sooooo extreme dynamic and has per user/per page/per request variations/tokens/role dependencies/dnt header dependency and many other. If I need to create a file for every of the variations I may create 127.500.000.000 files on d.o. It looks like nobody has confirmed this is a good and useful idea and I do not like to do this. It also makes combining impossible as this make the JS code uncached. Additionally this is tooo slow as JS code need to run once the header is send to the client and not after the footer JS has been downloaded.
  • I think some delays will be underestimated. The browser process is as follows (correct me if I'm wrong). I just added some high value seconds on it just to show the problem better. This may be faster in real, but it proves the delay problem that is ignored.

    1. Page downloaded (1s)
    2. Header evaluated, CSS files (100kb) downloaded (3s)
    3. Content rendered and NOT shown (white page)
    4. Other files like images and so on
    5. Footer JS files (500kb) downloaded (5s)
    6. Footer JS code executed (and now some configurable CSS is added first)
    7. JS settings are applied in 9th second first!

Caching:
Until now I have no idea why inline JS code cannot cached like any other JS or CSS code. I guess this is also a false argument. Caching Google Analytics inline JS code may not possible as it is tooo dynamic, but at least a hash over the code and compare it should solve the question if it has changed or not. A hash could also be used in Varnish to update the cache I think. But really I do not like to discuss cache questions here as it is OT and only confuses this case more. This can be discussed in a new case.

I'm not trying to make Drupal slow! Inline JS and CSS code is valid code and does not mean there is a security issue if written properly. That is why we have Json::encode.

nod_’s picture

StatusFileSize
new6.43 KB

Inline JS/CSS is markup and from what I understood a big problem is putting the markup in head and being able to order things.

In the spirit of moving things along here is a patch adding head_top and head_bottom regions, much like page_top and page_bottom. I added some code in block_head_bottom to show how it'd look like adding inline JS. It's a prototype but I'm hopeful that would solve most our issues here.

Since it's a render array, all the stuff we know and are used to apply.

As a side note, putting the GA stuff in drupalSettings is very much possible and would look like

// assuming drupalSettings.ga = [['create', 'auto', {}], ['myTracker.require', 'xxx']]; and so on
drupalSettings.ga.forEach(function (params) { ga.apply(window, params); });

Instead of inlining the js code, the params can be prepared and put in drupalSettings.

hass’s picture

Yes we have an order issue for sure. One of the most popular comes in mind with

window['ga-disable-UA-XXXX-Y'] = true;

This window property must be set before the tracking code is called. It to be set on each page where you want to disable Google Analytics tracking. At least the window['ga-disable-UA-XXXX-Y'] will be set by https://www.drupal.org/project/eu_cookie_compliance as I know and if the property is not set or set to false then the tracking will work as usual. With current html_head method this line may add this code after GA code and not before. This breaks the eu_cookie_compliance module and I do not like to provide an API for this myself. Please take this only an one simple example for issues that will occur.

I should have added more complex examples for GA and not only these simple key/value examples. So I add more below. Looking forward how you'd like to implement these with drupalSettings. I fear when it comes to the hitCallback than drupalSettings is out? For the rest I'm also not sure how, but _nod may be a much better JS developer than me - so looking forward to the ideas.

ga('create', 'UA-12345-6', 'auto', {'name': 'newTracker'});
ga('create', 'UA-12345-6', {
   'cookieDomain': 'foo.example.com',
   'cookieName': 'myNewName',
   'cookieExpires': 20000
});
ga('set', 'page', '/about');
ga('set', { page: '/about', title: 'About Us' });
ga('send', 'pageview', { page: '/about', title: 'About Us' });
ga('send', 'pageview', {
  'page': '/my-new-page',
  'hitCallback': function() {
    alert('analytics.js done sending data');
  }
});
ga('send', 'event', 'image1', 'clicked');
ga('send', 'event', 'image2', 'clicked');
ga('send', 'event', 'click', 'download-me', { useBeacon: true });
ga(function() { alert('library done loading'); });
ga(function(tracker) {
  var defaultPage = tracker.get('page');
});
ga('set', 'forceSSL', true);

_nod: The code you shared does not allow real weights.

nod_’s picture

Incomplete solutions will be given for incomplete examples. If you have functions then of course drupalSettings won't be enough.

_nod: The code you shared does not allow real weights.

Care to explain? As far as I know you can have #weight on render arrays. (and the underscore in my pseudo is at the wrong end)

hass’s picture

Nod_: sorry, but it looked like you only tried to create two regions. Please keep in mind that a region hook does not work here. For GA i need to run code at the latest possible hook that drupal has.

That means hook_page_attachments() only.

Why are we not able to make it easy and add a weight to html_head? Or the much better and more flexible "inline_js" attachments that allows setting up dependencies, too. If we cannot add js dependencies to libraries we have more issues I currently do not have, but others will.

Not only my ga specific issues need to be resolved here.

nod_’s picture

According to D8 render pipeline, hook_page_top/bottom run after hook_page_attachements, since my new regions just reuse code I expect them to run at the same time: even later than hook_page_attachements.

As for dependencies, it's a render array, #attach libraries away.

Did you try this before saying it won't work? They are regions sure, but they are specials ones.

hass’s picture

That hook order is something I missed. I justed migraded hook_page_alter() to hook_page_attachements() as documented, but yes, now we can switch a step later if there is no issue. Thanks for noting.

But with this solution we still cannot implement dependencies on libraries if a contrib requires this. This case is not just to make GA working... it goes bejond this.

I have not tried your code.

mikey_p’s picture

One major issue that I'm not seeing addressed at all in this thread is that it's currently impossible to append any markup after {{ scripts_bottom }}. This means that the only place it is possible to add inline script is to the head of the page, when we've moved all other javascript to the footer. This seems wildly inconsistent. @wimleers explained:

But… not a single 3rd party that asks you to embed some inline JS will rely on jQuery (or any other JS for that matter).

But there are a number of third party analytics and a/b testing tools that actually do use jQuery and check to see if it is loaded before adding it to the page. This means that putting an Optimizely snippet before jQuery is loaded may cause jQuery to be loaded twice.

Further, we work with a number of analytics systems that recommend that their snippet be placed at the bottom of the page, such as Marketo, CrazyEgg, etc. Even using '#type' => 'html_tag' won't work to add markup after scripts_bottom. This means that it is very difficult to develop a general use contributed module for these services.

Further, many of these snippets themselves are rather small, and while they could be moved to an external script with drupal.settings this would make adaptation of the snippets snippets from vendors difficult. Further, while js aggregation is possible, it seems to fly in the face of performance to not allow this very small (<1k) snippets to be added inline to the page.

Finally concerns over CSP related to inline scripts are mitigated in several ways, either through nonce or script-src hashing. The documents that @pwolanin quoted aren't especially relevant to this discussion as they are for developing chrome extensions, where things like nonce are not possible.

Jeff Burnz’s picture

I have a question about caching - if I inline CSS with html_head, e.g. $page['#attached']['html_head'][], and the CSS changes per page, will this affect caching?

What about per section/page suggestion (think front page, node pages) etc?

How should I handle this situation?

fabianx’s picture

#144: If you vary by page / full url, you will need to also add a cache context:

e.g.

$page['#attached']['html_head'][] = $data;
$page['#cache']['contexts'][] = 'url';

If you check routes instead you want the 'route' cache context, etc.

See https://www.drupal.org/developing/api/8/cache/contexts for more information on that topic.

kevinquillen’s picture

I agree with 143. It shouldn't be harder to do this in 8 when it was rather straightforward in 7. I also want to echo what was said in this comment:

https://www.drupal.org/node/2391025#comment-9627049

There are a number of Drupal 7 themes that have settings to enable or disable such functionality.

Looking from the outside in, webchick is pretty spot on here:

I legitimately do not understand why this issue is not being seen for what it is: a significant regression from D7, and something that everyone with a web development background will expect our framework to support

Does this also affect instances when you only want to attach css/javascript files to particular forms? How is that handled? Apologies if that has been answered above already.

frob’s picture

If support for this isn't in D8 then developers and users will get around it by doing hacky things, such as:

  • Adding a block that only inserts inline js.
  • Adding inline js tags straight in the template, such as #91

Or any number of things that I am not thinking of because I am not trying to do this right now. There needs to be a path of least resistance in the api to do the things that we need to do. Otherwise, developers will go outside the api.

adam_bear’s picture

This is definitely a bug that needs to be addressed...

Case in point, any time I add an asset from CDN I want a local fallback available, and inline scripts are needed to check if the object exists after an attempt has been made to load the external source before deferring to a local source, so pushing the `[#attached] ['html_head']` isn't viable. It'd be sweet to have 'object' & 'local' keys available to external js libraries and have core generate the fallback script based on those, but since this isn't a core feature I need to at least be able to implement my own solution.

wim leers’s picture

#148: That sounds like a pretty crazy edge case again. Especially considering that it's a bad practice already to load JS from "JS CDNs".

If you really want to do this, then it is probably wiser to do this in a much more structured manner. A Drupal 8 module to make it easy to override core's scripts to use CDN versions instead, if available, would make this a much more structured/standardized system. And it could do that by adding additional options to asset library definitions (e.g. jquery.js: { cdn: true }) and then override the \Drupal\Core\Asset\JsCollectionRenderer service with an alternative implementation that automatically generates those bits of fallback code for every JS lib that you try to load from a CDN.

See

adam_bear’s picture

Issuing http requests to multiple sources to parallelize download streams (+ possibility of script already cached) is a best practice- hardly an edge case.

droplet’s picture

Anyone can tell us how bad is it if we implement inline script in D8 CORE. Comment #134 only told us BAD BAD BAD, but never tell us what's wrong?

So what will be affected ?

1. Backend (PHP) Performance ? ( if so, how bad is it ? affected it by default config ? Or only advanced config to huge traffic sites.)
2. Code management ? (Missing Drupal Core Developers)
3. Front end performance ?
4. Personal preference ?
5. Acquia secrets ?
6. "Inline script" do not compatible with CSP enabled
7. etc.. ?

Personally, I won't accept #1,3,4,5,6 as points to ban this feature.
(about #1, unless it's huge impact of performance)

Just wanna know more :) Thanks!

alexrayu’s picture

And I would understand if the end user got restricted for some security reasons or because they could do something that could break things. But hereby the developers are the ones who get restricted. Because, if I understand correctly, it is believed, that the developers can compromise security and break things, unless they are thus restricted.

fabianx’s picture

#150: You have the possibility to use hook_js_settings_alter() to react dynamically, but that is not even needed here:

e.g.

hook_library_info_alter() adds the information, cdn: 'true' to every those JS you want to override and also a settings entry and a dependency on js_fallback_loader library.

Then you have js_fallback_loader library, which uses setTimeout(func, 0) to defer itself to check e.g. js_fallback_libraries: {source => dest} if all sources are present or at least in the process of loading and if they are not, then it loads the destination.

No inline script needed.

Also check out cdn_libraries module for 8.x-1.x, which could probably add such a fallback mechanism easily :).

frob’s picture

Title: Dynamic inline JS/CSS with #attached is no longer possible » Dynamic inline JS/CSS with #attached is no longer possible [No Patch]
Status: Active » Needs review

I have a couple of community questions about this issue.

  1. So is the issue here that we need documentation, or is this feature really just missing?
  2. While I cannot think of a use case where I would "absolutely need" to have inline js that is loaded by, or with regard to dependency, this really just sounds like our api is going to be getting in the developers way. Is that really what we want?
  3. If this is a feature that can easily be added in contrib then I do not think it should be in core, because core should be a place where everything is done the best way and allows things to be done the best way so long as core also allow contrib to do things completely wrong.

Really, I am fine with this for 8.0.x. If it is a major issue we can revisit in 8.1.x. Remember that is an option now.

[edit - removed some empty li tags]

hass’s picture

Title: Dynamic inline JS/CSS with #attached is no longer possible [No Patch] » Dynamic inline JS/CSS with #attached is no longer possible
Status: Needs review » Active
frob’s picture

Version: 8.0.x-dev » 8.1.x-dev

Referencing the doc page on attaching assets:
https://www.drupal.org/developing/api/8/assets

Changing this to a 8.1 issue.

johnvb’s picture

My scenario is I have a specialist external PHP and JavaScript user interface building library which we use within Drupal to build forms and reports which link to an external system. In D7 we have successfully built a node based configuration tool that site editors can use to create their own pages built using the external library. The elements added to the page each generate inline JS that gets added to the page. It needs to be dynamic because the site editors are able to build their own UI so we can't create static JS files, and re-engineering the external library to match Drupal's way of thinking is not an option.
At the moment none of the options I've tried in D8 are working. The referenced documentation idea of using hook_page_attachments does not work since the dynamic JS is added before other JS library files are referenced. I've also tried https://www.drupal.org/project/inlinejs but I've needed to hack it to get it working in beta, and the current release candidate is now applying HTML escaping to the output script so it is broken.

catch’s picture

Title: Dynamic inline JS/CSS with #attached is no longer possible » Add support for inline JS/CSS with #attached
Category: Bug report » Task

Moving to task, this is an API limitation, not a functional bug.

hass’s picture

This is a regression from D7 and this means a bug.

I'm now porting a theme and require dynamic inline CSS to be added as very last after all other CSS files are added. How should I solve this now?

mile23’s picture

In #156 there's a link to the documentation about this: https://www.drupal.org/developing/api/8/assets

Jeff Burnz’s picture

#159 this is pretty easy actually, you can write a file and use the dynamic library feature to create library, I do this in my theme, e.g. use HOOK_library_info_build() to build the library definitions and load the library as per normal in preprocess or HOOK_page_attachments_alter() etc, one assumes the CSS is a layout or similar based on settings/config the user selects in admin. If you are storing CSS in the database and loading it inline I think you're doing it the wrong way. Use the file/library system.

nod_’s picture

@hass: since you're porting the theme I assume you have control over html.html.twig file. If that is the case, adding a {{ cs_extra }} after the <css-placeholder> tag and filling that variable in a hook_preprocess_html() works. It does depend where in the process the CSS needs to be generated.

hass’s picture

@nod_: Ok, sounds like a workaround for now.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

gapple’s picture

Mentions of Google Analytics as an essential use case for requiring inline JS have tapered off, but I've implemented a module that uses drupalSettings in the manner that nod_ mentioned in #137.

https://www.drupal.org/project/gacsp

The remaining use cases that hass mentioned as requiring specific ordering or customization were window['ga-disable-UA-XXXXXX-Y'] = true; and hitCallback, both of which I believe have reasonable alternatives to inline scripts for implementation where they are needed.

frob’s picture

I agree, I don't see this as a technically limiting issue, as much as a hobbling developers issue. Yes there are technical ways to do anything I can think of that would need inline scripts. However, is this something that Drupal should be forcing. I don't really know.

droplet’s picture

@gapple,

Usually stats script should be available as early as it can. gacsp make something works but not working in best way. This is the point.

gapple’s picture

I wholly support core not using inline JavaScript so that it does not inhibit the use of Content Security Policy.
For a contributed module built for the general use of others, I think it is detrimental to inhibit others ability to make use of security features such as CSP. I think it is a reasonable restriction to not support inline JavaScript through #attached so that developers must actively circumvent a limitation that enables a security feature.
Enabling core to add a default CSP header would be a significant security benefit to any site implementer without knowledge of security practices by preventing a prevalent and easily overlooked security flaw.

For limited and specific uses of inline JavaScript, there are suitable alternatives to avoid the overhead of defining a library for modules (html_head), and themes (directly in templates).

----

It is untrue to say that gacsp’s method is not “the best way” to implement Google Analytics versus an inline snippet.

The execution flow for Google's analytics snippet is:

1. The snippet creates a global function ga(), that only appends the parameters of any call to a temporary array.
2. The snippet appends a script tag to the DOM for analytics.js with the async attribute set. The script may start downloading, but will not execute until after all other (synchronous) scripts have downloaded and executed.
3. The DOM finishes loading, synchronous scripts execute. This includes every JavaScript file added by Drupal.
4. Analytics.js is executed: the temporary array storing previous calls to ga() is processed, and ga() is replaced with a fully featured function that handles any calls immediately.

Therefore, whether any custom scripts for tracking are inserted inline or provided through a file, they will have no affect on the timeline in which the tracking data is actually sent, because analytics.js will not be executed any earlier.

alexrayu’s picture

I need to use this functionality and can't find a work around.

https://www.google.com/cse/cse.js?cx=api_key

api_key here are the Google GSE API key. The key is stored in settings. So, when attaching the external url call to the form, I need to substitute the API key from the settings to the string.

How can I do this in Drupal 8?

wim leers’s picture

See https://www.drupal.org/node/2402315. Let the API key be configured in a UI, then if it is set and valid, register an asset library with that URL in that hook.

alexrayu’s picture

Thank you, that solves the use case for me!

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

chriskinch’s picture

StatusFileSize
new3.63 KB

I have been following this thread for some time now taking in the comments about both JS and CSS.

Whilst inline JS has had lots of back an forth inline CSS has been recognised as important but not really solved or addressed. Inline (a.k.a critical) CSS is advisable for initial page rendering to eliminate the flash of unstyled content (FOUC) and is something recommended by various page speed tools. https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery

With that in mind I'd like to get some thoughts on the attached patch for core to add the functionality via the libraries system. This is only for CSS as I have seen many valid arguments for not having JS inline.

If this is something that is more suited to staying in a contrib module I've also thrown a simple little number together there too: https://github.com/chriskinch/css_inline

Apologies if this isn't quite the right place for this. There has been lots of good discussion here and I didn't want to lose that with a new thread.

cilefen’s picture

Status: Active » Needs review
hass’s picture

There exist inline JS for the same reasons like you noted. We still need both.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mitrax’s picture

This is one of the longest discussion and, unfortunately, during three years no one has provided any single explanation why attaching inline CSS and JS was removed as a feature.

Let's look the main reason for implementing the feature (there are so many other reasons, but this is the main one):

Inline CSS/JS is part of the HTML specifications. Period!

So why are there so much refusal to improve the feature that is already partially implemented (now it is possible to attach inline CSS and JS but only in html_head without any option for sorting and moving it after external files are loaded)?

Alexander Hass (hass) provided great examples why it's not possible to load external javascript files and on the other side, I'm completely stuck with a project where inline generated CSS styles are the only acceptable solution.

chriskinch created a module/patch for loading CSS but only via library system, not attached by value.

Back to the question: What is/are reason(s) not to implement attaching inline CSS styles and JS code and allow a user to insert them where they should be by HTML specifications?

nod_’s picture

I'm completely stuck with a project where inline generated CSS styles are the only acceptable solution.

Please elaborate, we can't know where the issue is without more details. As you might have seen there has been a lot of explaining and documentation updating (thanks to wim) done in this issue.

Back to the question: What is/are reason(s) not to implement attaching inline CSS styles and JS code and allow a user to insert them where they should be by HTML specifications?

On top of all the caching/performance reasons, it hasn't been necessary to address specific use cases raised as problematic.

skaught’s picture

a use case:

a project I am currently working. a sport site, where games results pages (a node bundle) shows tables for each teams players and their results for that game. the request is to have the table caption background be the teams color (which comes from a taxonomy with fields holding color, team logo, etc). so each page will have 2 sets of colors per table.

the possible options:

  • generating dynimc css sheet and loading per page load. such as https://www.drupal.org/project/simple_menu_icons does..
  • aside from using
    and masterminding the crud out of it in twig ...
  • INLINE: would be nice to use
    https://css-tricks.com/saving-the-day-with-scoped-css
https://www.w3.org/TR/css-scoping-1/
On top of all the caching/performance reasons, it hasn't been necessary to address specific use cases raised as problematic.
of course, is why this issue exists. caching/performance needs to be able to work with such dynamic outputs.
nod_’s picture

The first two solutions are much better to address your problem. Solutions exists and you don't seem like them. That was part of the reason for removing inline JS and CSS: not encouraging abusing inline js and css. Sounds to me like you need to brush up on twig, from what you told me it's doable and I would say it's the recommended way to deal with it. Having CSS and JS inside the PHP won't help with maintainability.

More generally the last two problems raised appear to be project-specific issues where (better) solutions already exist. If you read the thread you'll notice that previous use cases were around the GA module, a generic module that couldn't rely on twig to provide it's functionality.

mitrax’s picture

Finding solution for the particular issue here is not point (that's the reason why I didn't explain it in details). Simplified explanation of the problem is: dynamically generated CSS styles need to be generated for different elements on all pages per logged-in user.

- Pre-generating millions of external CSS files is simple not a solution.
- Inserting inline styles into head_html does't work because they are loaded BEFORE core style files.
- Handling styles via JavaScript only would be really ridiculous (besides the very ugly colors/styles transition effects hurting users' eyes)
- Attaching styles to e.g. page top region (after the body tag) is against defined CSS standards (styles need to be inserted in the head element)

Still not sure why inline CSS can't be cached in this situation.

And again, the point is not to find workaround but to use already very clearly specified standards. Inline CSS/JS is part of the HTML specifications.

We can't simply abandon specified universal features for the sake of performance. If so, we also can limit number of images inserted in content because they're loading too slow.

nod_’s picture

See #162.

mitrax’s picture

@nod_, Sorry for not mentioning that I'm developing a module that will be distributed, so changing themes is not solution neither.

nod_’s picture

Haha, fair enough. Did you see the solution I proposed in patch #137? Might work, no?

mitrax’s picture

As I understand, this patch actually create additional regions INSIDE the head element (between and ), correct? Inserting inline styles anywhere out of the head element is against defined CSS standards.

frob’s picture

I am not sure if that patch was ever committed to core, so if you are building a stand alone module you might need to try something else.

Why can't your styles be included before core's styles?

mitrax’s picture

@frob, I want to override styles from core CSS files with dynamically generated inline CSS styles. They can be included in html_head but only above core CSS files. In that case styles from files will be applied just like dynamically created styles are not inserted at all.

hass’s picture

@_nod: the arguments given by wim against inline js and css are simply wrong. It is not correct that this is insecure if done properly. And there are good reasons to add inline css for rendering reasons on mobile. Wim ignored me as I proved him that he is wrong with Google presentations I linked too. Using js setting is a shity workaround and nothing more - no solution.

donquixote’s picture

@hass: Did you notice #2890438: Clean way to avoid inline js? in Google Analytics queue?

To be clear: I still think that it would be reasonable to re-add native support for inline js and css in core.

However, I do think in case of GA a settings-based solution would be preferable to the code generation approach in google_analytics_page_attachments(). Honestly, this code generation looks quite fragile.
In an ideal world, this choice should be made by the module developer, not by the framework / CMS.

frob’s picture

@MitraX, Can you just make your inline styles more specific? Wouldn't that override the core css that is loaded after?

mitrax’s picture

No, @frob, CSS doesn't work that way. The last loaded styles will be applied,

It's funny that after 3 years we're still discussing whether or not to implement HTML standard that exists from the first day of HTML.

donquixote’s picture

@MitraX: https://developer.mozilla.org/en/docs/Web/CSS/Specificity
Maybe changing .myclass {..} to div.myclass {..} is already sufficient.

This said, I still think it would be preferable to have the option for inline styles that come after stylesheet files, so one does not need to add unnecessary CSS selector levels.

mitrax’s picture

@donquixote, thank you for your efforts! But, believe me, it's not so simple in this case. However, even if it's possible it would be really ugly workaround that could be broken by small changes in original CSS file.

I'm sure that we all together here have same goals, i.e. to make Drupal better, and that's the reason why I'm a part of the conversation and the community.

@hass provided great VALID example for using inline JS and I provided VALID example for using inline CSS.

And again, here are the facts:

- The feature is partly implemented through html_head, so all stories about security and caching simple are not valid
- The feature existed before Drupal 8, so it's downgraded now
- The feature is one of the first HTML standards, so (as you already said) choice should be made by the module developers whether they will use it or not

I'm simple trying both to find out why core HTML standard is downgraded from D7 and how I/we can help to improve the feature and the framework/CMS in general.

johnny5th’s picture

@MitraX, a higher selector specificity will override the cascade of css further down the page. However, sometimes being heavy-handed with specificity will cause a bunch of other cascade issues (looking at you, Bootstrap.)

But I'll agree with your sentiment. If it's possible to inject CSS into the head using these workarounds, stop making it so hard to do so. My main use case is critical, non-render-blocking css. I had to jump through a bunch of hoops to attach it to head, including creating a MarkupInterface derivative to keep Drupal from stripping descendant selectors (.parent > .child) out of it

mitrax’s picture

Not so simple, the higher selector for some elements are options in the backend of the module. For example something can be created as a div for one user, span for another user, a tag for the third user etc.

You see, the conversation turned itself to searching for solution for the single particular problem and I wanted to avoid that.

I simple would like to have implemented one of the oldest standardized HTML features, no more, no less.

hass’s picture

@Doquichote: i explained above that setting are not possible and why. Maybe you read all first before commenting.

droplet’s picture

It must be something I don't know. It's interestingly these 2 way doesn't documented.

  $form['yoyo'] = [
    '#markup' => '<script>console.log("Be Happier");</script>',
    '#allowed_tags' => ['script'],
  ];

  $form['yoyo2'] = [
    '#type' => 'inline_template',
    '#template' => '{{ js|raw }}',
    '#context' => [
      'js' => '<script>console.log("Not the evil");</script>'
    ]
  ];

Anyway, I still support to add a native API. It's more controllable.

If the API forced to rewritte the snippets, it's always wrong!

mitrax’s picture

@droplet, I found similar examples somewhere and I've already used the code in the module. It'spossible to add inline CSS and JS anywhere in the page except in the head element, in order you want, i.e. after css/js files are included. Using inline styles out of head element is not valid by the standard.

frob’s picture

@MitraX, just have your module add a body class and use that to increase your specificity. Thus all your css rules will be prepended with body.class-name .some-other-class

I agree it isn't pretty, but it should solve your specificity problem.

On the issue sake, the tide has turned in favor of better supporting inline css. And we should better support that. My preferred support would include full library support of injected inline css. Just make it an option that the aggregated css be injected as is along with or in-place of the css in the header as it is. But this issue is more about in JS and the inline CSS should probably be handled in another issue.

I am not sold on dynamic php generated js being a good idea. And just because something is in the spec, doesn't mean we should support it if it is obviously not a good idea. The drupalSettings solution is made for this problem. JS that is dynamically generated from php is bad because it removes the ability to do proper unit tests and linting, and it is obviously a bad idea for all the reasons everyone else has stated throughout this issue. I knew that when I wrote js that was generated by php. It would be great if that can be conceded. However, inline js has it's place and we have allowed for that in the case of twig templates. Is there another way the API needs to change that can support more complicated inline JS that is not generated by PHP?

I am just throwing ideas out, but what if we allowed JS to be included inline from a .js file? I am not saying it is a good solution, I haven't thought it through that far. I am only saying this is a possible change to the api that would allow for JS to be written in JS files and still be included inline.

mitrax’s picture

As it's mentioned in the issue's description, PHP generated Inline CSS/JS code is already allowed in the head via html_head. The issue is all about improving the ability to control it's weight and location like it was possible in earlier versions

mitrax’s picture

Besides that, @droplet provided simple code in the example above that inserts into the body PHP generated "whatever-you-want-where-ever-you-want" code, so why it's not possible to have control over head? Particularly because it's already implemented?

I could open another issue dedicated to CSS but the root of the problem would still be the same.

nod_’s picture

Status: Needs review » Needs work

Having control over head is a different issue. Please open one. There are about 90 people watching this huge issue. Let's make an effort to keep things on topic.

We talked a lot and this is not solved. Because we don't agree doesn't mean we won't review patches. Someone should get a patch started.

donquixote’s picture

@nod_: What if we treat this one as a "policy" or "plan" issue, and open new issues for patches?
At least for inline CSS, I have not heard that much opposition, so we could open an issue for patches.

Upload patches here would mean that reviewers feel compelled to read the entire issue, which I think we should avoid.

And before we develop patches, maybe we should define the desired API?
Which can also happen in the new issue.

mile23’s picture

I'm simple trying both to find out why core HTML standard is downgraded from D7 and how I/we can help to improve the feature and the framework/CMS in general.

I'm pretty sure this came about in order to force people to take CSS out of render arrays, because it's much more maintainable and flexible that way. I could be wrong, of course.

It looks like #173 allows you to inline a CSS file, which might be a step in the right direction. Review that code and we might be able to move forward on it.

adam_bear’s picture

I'm pretty sure this came about in order to force people to take CSS out of render arrays, because it's much more maintainable and flexible that way.

This issue forced me to forget about D8... D7 wins on performance and doesn't arbitrarily limit my choices as the developer.

adam_bear’s picture

My preferred support would include full library support of injected inline css.

@frob: Yes. & js.

mile23’s picture

Review of code generally helps move things forward. There's a patch in #173.

mitrax’s picture

@nod, I'm am on the topic. This is the original issue's description at the very begining, it states:

Issues:

  • JS cannot attached with 'html_head' as the order is random.
  • The order of code is broken as inline JS need to be added after JS files and not before.
  • CDATA is not added automatically and could be theme specific
  • Inline JS code cannot moved to files in many cases as these are dynamic and may not automatically drupalSettings.

I'm talking ONLY about the issue described above. Have you ever asked yourself why so many people are looking at the issue? Because we all have the problem, standard feature that existed in previous versions is downgraded, that's the reason why.

Comment by @webchick:

I legitimately do not understand why this issue is not being seen for what it is: a significant regression from D7, and something that everyone with a web development background will expect our framework to support, since inline CSS/JS is part of the HTML spec. Particularly given the apparent recommendations about inlining CSS/JS for better mobile performance.

Neither do I understand.

Guys, please read detailed explanation above why using external files is not an option, I simple cannot create milions of them.

@hass provided clear and valid example for JS and I provided clear and valid example for CSS.

Regarding the patch #173, as I mentioned in a comment above, it works only for external files. This is not even close to the solution to my particular problem where CSS need to be generated dynamically and inserted below already included files. @hass has completely the same issue, but with JS code instead.

Have you seen the patch #137, it looks promissing. It actually adds "virtual" blocks above and below original head's content (inside head element) so developers can attach whatever they want before/after the original content without affecting any core logic.

frob’s picture

Guys, please read detailed explanation above why using external files is not an option, I simple cannot create milions of them.

I have not seen an issue that would require this.

@MitraX, hass' code can be refactored to use drupalSettings and thus not require millions of iterations of the same file. In fact, if that is a requirement for your code then you should seriously consider refactoring your code anyway because it is unmanageable.

Please take note on comments 203, 204, 205, and 208. People are trying to help solve this issue. We want this resolved. But simply making the argument that D7 allowed us to do something that was clever, but probably a bad idea, and trying to simply make things the way they were (in D7) isn't going to move this issue forward.

With all that said. I agree with donquixote. This issue might be better served as a PLAN issue and the patches moved to another issue to aid in the review. Although, I have a feeling anyone doing the review is already following this issue.

droplet’s picture

I think we should STOP telling others what they should do. In the last few comments, they understand how to achieve their jobs but they've rejected the workaround since it's not pretty.

Instead, can anyone summary the issue we faced and the reason that rejected to implement this feature. Then, we able to seek a way to out.

Before the patching, I think you able to override the service html_response.attachments_processor ( \Drupal\Core\Render\HtmlResponseAttachmentsProcessor) to insert your code. (which may more flexiable than hacking JS/CssCollectionGrouper). This is how BigPipe does also(??). Someone worked on BigPipe may able to share more info to us.

<css-placeholder token="{{ placeholder_token }}">
<js-placeholder token="{{ placeholder_token }}">

And these code, I'm interested if this is dynamic inserted ( \Drupal\Core\Render\HtmlResponseAttachmentsProcessor::renderHtmlResponseAttachmentPlaceholders ) or caching. If you can share your knowledge, that will save my day to dig into it.

I think some littlie API extention between these functions will get thing done and need not to modify the Twigs.

nod_’s picture

the reason that rejected to implement this feature

To me inline JS and CSS is simple markup, just like <details> or any other html tag. It has nothing to do with #attached.
If we treat script and style like other tags it means we can have those generated scripts and css in a twig template and make it easy to override should we need to (and for me generating js/css from a twig template is better than from PHP).

The fact that we can't sort what's in the head of a page is an issue and that's the first thing we should fix here, so that we can use all the things that already work such as render cache, cache tags and all without adding more code and special cases just for inline js and css in #attached.

andypost’s picture

Priority: Major » Normal

There's inline_template is not it enough to output things like GA & other inline?

hass’s picture

Priority: Normal » Major
donquixote’s picture

@nod_ (#212):

To me inline JS and CSS is simple markup, just like or any other html tag. It has nothing to do with #attached.

If I understand you correctly, the main concern of you (and possibly others) is not inline CSS or JS by itself, but it being added via #attached and render arrays.

On the other hand, I get the impression that people who say that they want native support for CSS or JS do not care that much about render arrays and #attached, and would instead be happy with any other method to add inline CSS or JS to a page.

So, could we think of an alternative to #attached in render arrays?

The fact that we can't sort what's in the head of a page is an issue and that's the first thing we should fix here, so that we can use all the things that already work such as render cache, cache tags and all without adding more code and special cases just for inline js and css in #attached.

Well usually you care about the position of one inline JS or CSS relative to another inline or non-inline JS or CSS.
You usually do not care about the position of one inline JS vs another random html tag. I'd say you usually want stuff in the header grouped by "type", e.g. first all <meta>, then all <link type="text/css">, then all <script>. A random mix of those tags would not end the world, but it would look untidy.
Also it can be useful to centralize how inline JS and CSS elements are formatted ("themed"), so that modules only need to provide the raw script or styles, not the html tags.

donquixote’s picture

I'd also say that the "natural place" for inline scripts / styles is after the non-inline ("external") scripts / styles.
For scripts, this would mean that inline js can use symbols from external scripts.
For styles, it means that inline styles automatically override external styles with equal precedence.

Having this as the default behavior would mean that most modules would not need to explicitly control the order of things.

hass’s picture

  • We need #attached
  • We need all dependency features of #attached
  • We need the caching of #attached
  • We need the ability to disable caching if needed (dynamic code that change on every page for every user and request)
  • We need the automatic ordering / weight features
  • We need the ability to add whatever js or css code we want in head.
  • Drupal settings are not flexible enough as these do not allow to embed native js code.
  • It is a lie by wim (repeated very often) that inline js / css is insecure. A clear false argument.
  • Inline css is MOBILE FIRST.
  • Inline js / css does not disallow bigpipe. The argument was given by wim, but never proved.
  • The removal of inline support must have never been committed to core and need to rolled back.
  • We do not want to reinvent APIs from 3rd party software.
droplet’s picture

It is a lie by wim (repeated very often) that inline js / css is insecure. A clear false argument.

At least, Inline CSS should be secure. And while we able to use #markup & inline_template to inject JS. I can't see the point of secure by default also.

Inline css is MOBILE FIRST.

More than Mobile.

https://developers.google.com/speed/docs/insights/PrioritizeVisibleConte...

There're acutally have inline JS techquies to load fonts and JS/CSS/Res, e.g:
https://www.filamentgroup.com/lab/font-events.html

The external scripts increased extra round-trip on slowing network/server would cause slow performance rendering.

We do not want to reinvent APIs from 3rd party software.

"Our program policies do not permit any alteration to AdSense code" - It's why we should STOP telling others to do refactoring of their code.
https://support.google.com/adsense/answer/1354736?hl=en

I remember the old addthis script forced you to add one inline code + follow one external script. Yeah, it's a bad design but they do. I used to help clients install many crazy ad services, they intented to force the page render slowly in favour of their script to capture more things and ensure loaded before full page rendering. (It's a way to increase conversions. Silly right but $$$)

frob’s picture

This issue is about two different things that may not need the same solution. Inline JS and inline CSS. The spec handles each of them differently and we shouldn't think that a single solution should work for both.

I have created #2891967: [PP-2] Support inline and async CSS to track and discuss a fix for the problem of inline CSS.

The fact that we can't sort what's in the head of a page is an issue and that's the first thing we should fix here, so that we can use all the things that already work such as render cache, cache tags and all without adding more code and special cases just for inline js and css in #attached.

Let's solve this issue with JS.

mile23’s picture

Title: Add support for inline JS/CSS with #attached » Add support for inline JS with #attached
Issue tags: +Needs issue summary update
Related issues: +#2891967: [PP-2] Support inline and async CSS

Rescoping here. Obviously, change as needed. :-)

hass’s picture

Title: Add support for inline JS with #attached » Add support for inline JS/CSS with #attached

Both has been removed with one patch and is one patch. Logic is 100% the same.

Both requires #attached because of dependencies logic. Things must not splitted up as this would only split priorities. Less people working on same logic patch in two issues. Waste of time.

mile23’s picture

Looking forward to a patch, then.

chriskinch’s picture

I can see that the complication of this argument is clouding one of the solutions we have, at least for external inline CSS only. I think I'll make a separate issue for my patch and see if I can get it in that way. Maybe the discussion will be more focused there. I'll be sure to reference this thread.

frob’s picture

Priority: Major » Normal

@chriskinch, I already created an issue for CSS #2891967: [PP-2] Support inline and async CSS take a look and see if it applies to your patch.

droplet’s picture

What are we going to support or not support?

I'd like to hear 4 feedback:
1. What if Performance doesn't matter
2. What if Performance does matter
3. what if inline JS security doesn't matter
4. what if inline JS security does matter. If it does, how possible to add "nonce"

Here's a possible pattern:

<html>
  <head>
    <style>inline CSS</style>
    <script src="external.js"></script>
    <script>inline JS</script>
    <script src="external.js"></script>
    <script>inline JS</script>    
    <meta />
    <link type="text/css" rel="stylesheet" href="external.css" />
    <script src="external.js"></script>
    <script>inline JS</script>
    <link type="text/css" rel="stylesheet" href="external.css" />
    <script src="external.js"></script>
    <meta />
    <script>inline JS</script>    
    <style>inline CSS</style>
    <link type="text/css" rel="stylesheet" href="external.css" />
    <style>inline CSS</style>
    <link type="text/css" rel="stylesheet" href="external.css" />
    <style>inline CSS</style>
    <link type="text/css" rel="stylesheet" href="external.css" />
    <style>inline CSS</style>
  </head>
  <body>
    <script>inline JS</script>
    <script src="external.js"></script>
    <script>inline JS</script>
    <script src="external.js"></script>
    <script>inline JS</script>
  </body>
</html>
droplet’s picture

This is current drupal.org pattern: (I expected this is the minimum requirement we must achieve?)

<html>
  <head>
    <meta />
    <script>inline JS</script>
    <meta />
    <link type="text/css" rel="stylesheet" href="external.css" />
    <script>inline JS</script>
    <script src="external.js"></script>
    <script>inline JS</script>
    <script src="external.js"></script>
    <script>inline JS</script>
  </head>
  
  <body>
    <script src="external.js"></script>
    <script>inline JS</script>
  </body>
</html>

I heard that d.org spent a huge amount of cost on it. No idea why it's not a good citizen to demo something Drupal 8 don't like and how possible to re-code newrelic tracking scripts. Even it's designed for a SINGLE site only.

frob’s picture

@droplet, could you post some inline comments to let us know what you are trying to demonstrate with those code samples? I don't really follow.

skaught’s picture

re: #225
a lot of people aren't aware of placing code after </head> before <body>

droplet’s picture

@frob,

That's a code pattern that HTML5 web standard allowed. I would like to understand what Drupal will not support and the limitations. (It will help to patch and reviews)

Also, there're 2 reasons I often heard from this thread:
- "Fast by default"
- "Security by default"

What's the meaning of that? and for Core, for Module, or custom sites, or all. It's a dead loop again if we have no answers.

*** BTW, In real life, we will insert <style> inside BODY tag also even it will not pass web standard. (Browser still parsing it)

frob’s picture

Thank you droplet, I didn't know what you were trying to point out with those demonstrations. Now I know.

it will not pass web standard. (Browser still parsing it)

We shouldn't have anything in core that doesn't pass validation, even if the browser parses it. Browsers are built to fail soft and they expect the code parsing to be broken. Thus they attempt to fix the html if it is invalid. So, "it renders" is not a good argument for something being included.

I have worked on many projects where passing validation is a legal requirement for the project. Meaning that if the html doesn't validate then the project cannot be published. Also, it is a bad president. If we allow for invalid html, just because the browser parses it, then what becomes the standard and who dictates what invalid html is allowed and what invalid html is not allowed.

All of this is off topic, I thought that we just wanted a way to use #attached to put js in the head, while respecting the dependency tree of the other external js that is loaded.

droplet’s picture

@SKAUGHT

Hmm. It doesn't allow actually. I will remove it since it's misleading. thanks!

---

But there was a technique to put code after end </body> tag for non-blocking rendering. (non-standard also)

skaught’s picture

'allowed' maybe not -- it does work. back in time.. i used this placement to trigger things like 'page loading effects' as it will trigger script before body is rendered. same effect as after Body tag.

#229
as i've pointed to in #180 @scoped -- it is a thing!!! https://css-tricks.com/saving-the-day-with-scoped-css

aheimlich’s picture

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

chriskinch’s picture

Could we potentially distill this issue to just being about JS only? The JS discussion seems to be holding up what I think most agree is not an issue for CSS. Even the description barely mentions CSS and there is a separate discussion going on about CSS here: https://www.drupal.org/node/2891967#comment-12229049

hass’s picture

This is not needed. The code will be nearly the same - at least logic wise. If you can come up with the CSS part we can easily adapt the JS code.

chriskinch’s picture

I have posted the patch in #173 for the CSS part if you'd like to review? Not sure if it's what we need but it's being doing the trick for me.

progzy’s picture

@chriskinch thank you for your patch ; I've tested it and I've noticed a problem. Some chars get escaped.

This is a problem with such css:

li > a

which get converted to:

li &gt; a

So obviously css does not apply in this case.

It seems it could be fixed in "core/lib/Drupal/Core/Asset/CssCollectionRenderer.php" file by adding on line 7 :

use Drupal\Core\Render\Markup;

and then updating on line 213:

$element['#value'] = Markup::create($this->getCssFileContents($css_asset['data']));

Works for my case.

hass’s picture

Sounds the same like #2821815: HTML escaping characters in snippet before and after for GA. You may like to take a look and steal some code from there...

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

leewbutler’s picture

The following pattern can be used. Note the use of twig's "raw" filter...

/**
 * Implements hook_preprocess_html.
 */
function my_module_preprocess_html(&$variables) {
  // To get around the issue of D8 core escaping inline js we cram our js
  // file into a twig variable for printing "raw" in "html.html.twig" like so: 
  // {{ my_module_script|raw }}
  $variables['my_module_script'] = '<script type="text/javascript">' . file_get_contents(dirname(__FILE__) . '/js/script.js') . '</script>';
}
andeersg’s picture

I legitimately do not understand why this issue is not being seen for what it is: a significant regression from D7, and something that everyone with a web development background will expect our framework to support, since inline CSS/JS is part of the HTML spec. Particularly given the apparent recommendations about inlining CSS/JS for better mobile performance.

I totaly agree with this, and it's something I would expect a large CMS should support. Also I don't see why it's Drupals job of limiting what people can do based on good/bad/horrible practices. I think it is better to have a proper way of doing this than a lot of hacky ways. That lowers my impression of over-all quality in Drupal.

And pretty much every hook and overridable thing can be used to create crappy things, but that responsibility to not do that lies with the developers.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mitrax’s picture

I have been following this topic for almost 4 years and still not sure whether we have agreement that this feature should be implemented or not.

I can also see that the issue is (unnecessary) split into several different "related/referenced by" issues. And YES, this is still contributed projects blocker!

Can someone confirm that the issue/feature (for both CSS and JS) is in plan to be coded or not?

mile23’s picture

Issue tags: +Needs tests

The most recent patch is in #173. If you'd like to move this issue forward, please review it. See also the various tags, the most important being 'needs issue summary update.'

The patch in #173, through some black magic, still applies. :-)

The 'contributed project blocker' tag was applied by @hass in #31, which was in 2014. It's unclear what contributed project is being referenced, and one hopes there has been an unblocker in the intervening 3+ years.

The 'frontend performance' tag was also applied by @hass in #88, which seems to be referring to #87, which was in 2015. This tag very likely still applies, because we will need a solution that works with page caching and big pipe, and which is otherwise performant.

I'm adding 'needs tests' because the patch in #173 does not have any.

In short: 'Needs work.'

jamesoakley’s picture

I can give one instance: Until very recently this was blocking Pingdom RUM.

It now isn't, because Pingdom have changed the syntax of the JS they require so that it no longer requires an inline script section. But until they made that change, I was stuck to know how to start a D8 version.

@hass maintains things like Google Analytics, so I suspect it's a similar issue.

hass’s picture

Matomo and Google Analytics

gapple’s picture

The Googalytics Drupal module provides Google Analytics support without inline scripts.

Matomo 2.15.0 can be used without inline scripts for compatibility with Content Security Policy: https://matomo.org/faq/general/faq_20904/


The 'contributed project blocker' tag doesn't seem like it's still relevant.
I assume with 40,000 D8 installs, google_analytics is effectively using one of the alternate methods of inserting inline scripts that has been mentioned previously in this issue.
There hasn't been any other mentions where modules are unable to operate without inline scripts using #attached, only that it would be preferred by some developers to the available methods.


The 'frontend performance' tag seems to be in regards to mobile performance over high-latency connections due to external files requiring additional HTTP requests.

  • Additional requests are reduced by aggregation. In most cases a module introducing an additional asset will not require an additional aggregated file, so the number of HTTP requests will be unchanged.
  • Inlining assets increases the bytes transferred for every page request. Browser caching of external assets reduces total bytes transferred, speeding up every request after the first.
  • HTTP/2 addresses the limitation of HTTP 1.1 requiring multiple connections (and being limited in concurrent connections) by operating over a single multiplexed connection. Other performance optimizations such as header compression and stream prioritization further reduce total bytes transferred and perceived performance.
    Safari was the last mobile browser to add support for HTTP/2 in September 2015.

If any other modules are having problems with a service that only provides a snippet that uses the inline document.createElement('script'); pattern to include their library, I'm happy to help them convert it to work with Drupal 8's libraries API (and drupalSettings).

gapple’s picture

I'm hesitant to propose this, but a contrib module could wrap or alter the relevant core services to enable inline #attached assets. Other modules that insist on using inline could then rely on it as a dependency.

The module could potently maintain compatibility with Content Security Policy restrictions by aggregating the inline attached scripts and writing them to a file instead of inserting directly into the page markup.

mile23’s picture

Removing 'contributed project blocker.' If it's still actually blocking contrib, please add it back with a related issue so we can keep track of what's blocked.

hass’s picture

This blocks propper implementation of Google Analytics, Matomo, ReCaptcha and others.

@gapple: it is bullshit what matomo guys have written there about CSP. This only works for the limited example they given. It goes not work for advanced tracking. Let me know how you add a js function inside drupalSettings.

Http/2 does not solve anything. Without inline css you will have rendering flashes.

frob’s picture

I split the CSS issue (which the patch from #173 fixes) off of this one to see if it could get traction.

Hass will likely disagree, but this issue is primarily about JS and the CSS is more of an add-on. There was some momentum about a year ago, but it has sense stalled. #2891967: [PP-2] Support inline and async CSS

gapple’s picture

@hass You've created stable 8.x releases of all three of those modules. Are you able to link to particular issues for those projects regarding functionality that they cannot currently implement?


This only works for the limited example they given. It goes not work for advanced tracking. Let me know how you add a js function inside drupalSettings.

This is difficult to fully respond to without a clearer example of "advanced tracking".

_paq.push() can be provided arbitrary arguments with Function.prototype.apply(), so a small wrapper can push tracking commands with arguments provided via drupalSettings.

hass’s picture

@gapple: You need to enable all features the module supports including 25 dimensions and metrics with tokens that change values on every page, userID enabled, messages tracking (page and error depended), search and advertising and than add below code as-is to drupalSettings json.

'hitCallback': function() {
    alert('hit sent');
  }

I'm listening how you make this last wonder happen as json_encode() disallow this.

And last but not least, you need to develop one module that depends on GA. This module now needs to insert JS code before GA module (e.g. eu cookie compiance) and after ga script tag - all without changing the module weights and hidden helper modules. This is also no longer possible since D8 as html_head has no weight feature anymore.

Additionally your are not allowed to change the default implementation (code snippets) provided by Google as hundreds of users will ask you in your modules queue - why it is different - and Googles Support will also tell these users to change the code so it looks like the code documented on Google API pages. Otherwise they do not get support from Google. These support guys also complain if you are not using single quotes (as documented), but double quotes as they have no idea about javascript. No joke...

Good luck... I'm looking for your fully working code examples! Please share them if you can solve all these issues (not just a few). Otherwise be honest and say - this is really not possible since D8 as Core developers have broken it.

_paq.push() can be provided arbitrary arguments with Function.prototype.apply(), so a small wrapper can push tracking commands with arguments provided via drupalSettings.

Can you share an example, please?

mile23’s picture

@hass: Please file the issue on GA and link to it here, and then we can keep the 'contributed project blocker' tag.

hass’s picture

I do not need to have such cases in every of my modules. Once we fix the issue I will fix my modules. Fixing my modules also do not require a case.

adam_bear’s picture

It doesn't surprise me that even though this issue was first opened 4 years ago there is no work around- there are a million moving parts that work together, and it isn't always easy to get things to work properly.

It does surprise me that even though this issue was first opened 4 years ago core devs still refuse to even acknowledge that this is a valid problem with the architecture.

My workaround has been to continue to use D7 and work more with py/django for when drupal stops supporting D7- If I'm going to invest time in learning and developing with an API, it's got to be 1. capable (the fail point here) 2. stable (being currently incapable, system is inherently unstable) 3. worth the investment (incapable unstable system === not worth it)

Good luck guys!

donquixote’s picture

@Mile23 (#255)

@hass: Please file the issue on GA and link to it here, and then we can keep the 'contributed project blocker' tag.

I opened #2890438: Clean way to avoid inline js? once, but it went nowhere.

gapple’s picture

I was unable to find any usage of hitCallback in google_analytics' code, nor any references in the module's issue queue.
The instances where I've required using hitCallback have always been in a Drupal behavior which could be passed any required arguments via drupalSettings, so I have not seen an instance where the inability to serialize and evaluate arbitrary JavaScript functions was a practical limitation. A link to a relevant issue may justify the 'Contributed project blocker' tag, but the particular example you've presented seems like a theoretical concern.


Here's an example of using apply().
hook_page_attachments() has control over the order of tracking commands, and is able to insert dynamic values as needed.

function matomo_page_attachments(array &$attachments) {
  $trackerUrl = 'http://example.com/piwik/piwik.php';
  $siteId = 1;

  $attachments['#attached']['library'][] = 'matomo/matomo';
  $attachments['#attached']['drupalSettings']['matomo']['commands'] = [
    ['setTrackerUrl', $trackerUrl],
    ['setSiteId', $siteId],
    ['trackPageView'],
    ['enableLinkTracking'],
  ];
}
(function (drupalSettings) {
  var _paq = _paq || [];  

  for (var i = 0; i < drupalSettings.matomo.commands.length; i++) {
    _paq.push.apply(this, drupalSettings.matomo.commands[i]);
  }
})(drupalSettings);
hass’s picture

@gapple: You seem not understanding that this module does not implement everything out of the box, but need to allow everything that is possible with the Google API. Other Drupal modules also extend the functions of GA module by altering and reordering stuff.

Please read https://developers.google.com/analytics/devguides/collection/analyticsjs... and https://developers.google.com/analytics/devguides/collection/analyticsjs... if you need more information. You can also add such hitCallback into create/before/after fields of advanced settings. All need to be possible.

(function (drupalSettings) {
var _paq = _paq || [];

for (var i = 0; i < drupalSettings.matomo.commands.length; i++) {
_paq.push.apply(this, drupalSettings.matomo.commands[i]);
}
})(drupalSettings);

Ok, got it. Please add JS functions to drupalSettings.

Can you follow up to comment #254 and provide the full examples and modules with weight in html_head, please? This is crucial.

frob’s picture

Hass, I have been following this issue from the beginning. I have read the google development pages. I have taken a deep look into the Google analytics module. I do not understand why you absolutely need JS functions in drupalSettings. That is just a bad idea and a security issue.

I agree that we should have inline JS that respects weight and library dependencies. I am not sure this is an actual contributed project blocker, #2391029: Attaching inline JS no longer possible. Help get this back into core! happened and looks like the 3 year old unstable fix is working. What did that fix do that is unstable, how would you change the GA module if you could roll back the commit that fixed #2391029: Attaching inline JS no longer possible. Help get this back into core!

hass’s picture

@frob: Where should this bring us, too? It is like the discussion that inline JS and CSS is bad and insecure - what was and is a lie.

This is valid js code. I need to support the full Google API, not just a bit.

gapple’s picture

Inline JS and CSS is not by itself insecure.

Arbitrary, user-editable CSS and JS is a security risk.

Using Content Security Policy and not opting-in to allow inline scripts is an effective way to mitigate the risk of Cross-Site Scripting vulnerabilities.

frob’s picture

Please re-read my post.

[Regarding] JS functions in drupalSettings. That is just a bad idea and a security issue.

JS functions in JSON is a security issue. It is, that is why the spec for JSON was changed to not be parsed with eval.

With that said, you can should be able to pass JS code into text and add that to the value of the JSON object and then use eval on that code. If you absolutely need to add a js function as a value to a JSON object.

Let me end with another quote from my last comment.

I agree that we should have inline JS that respects weight and library dependencies.

In my opinion this is a huge hole in the way JS and CSS is now handled in Drupal and a huge DX issue.

hass’s picture

Another now known issue that is caused for the reason we cannot order inline in header properly.

frob’s picture

Issue summary: View changes

I hesitate to do this due to the Hass firestorm that might ensue, but I am updating the IS so that maybe this issue can get resolved.

dpagini’s picture

The related issue added in #265 is sort of a whole different can of worms. Due to html.html.twig printing the head-placeholder, title, css-placeholder and js-placeholder, in that order, you can never render the inline javascript (in html_head) after the title tag, which is what seems to be the recommended fix for this issue. So it's going beyond just rendering inline js in the head tag and controlling the order...

If the google_analytics module converted to a library, it could be rendered after the title element... just wondering, is there a functional reason why title is not built as part of html_head? Then if there were weight respected on the #attached items, you could move an item around other elements.

Having said all that... it seems to me like weight DOES work in html_head. I took the below script from the google_analytics module and added the weight line, and when I experiment with that value, I am seeing the output script tag being moved up and down relative to the other elements.

    $page['#attached']['html_head'][] = [
      [
        '#tag' => 'script',
        '#attributes' => [
          'async' => TRUE,
          'src' => $library,
        ],
        '#weight' => 400,
      ],
      'google_analytics_tracking_file',
    ];

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

anybody’s picture

Yep, I can confirm #267!
Just worked at an issue for google_analytics.module and also played with the weight, it jumps around above , but never gets lower than <title>.

So after reading a lot in the several issues I completely agree with @hass that the current solution is lacking a possibility to flexibly control the position / weight of dynamic generated CSS and JS like it is possible for libraries. Of course dynamic CSS / JS should still be an exceptional case, but that's what each module developer should decide and not what Drupal should enforce.

An example where this is for example needed to work in the widely used Google Analytics module can be found here:

Furthermore I ran into the same issue with several other tracking libraries like Facebook Tracking Pixel, Matomo, ... especially if you need special features or GDPR opt-out functionality.

So I close with #178 by @mitraX:

Inline CSS/JS is part of the HTML specifications. Period!

We can still add a red blinking block to the documentation that this is only intended for edge cases and should only be used if you know what you're doing. Perhaps even with the required implementation of an own MarkupInterface class to make it harder, but please... let us allow CSS & JS in the head area after link / scripts with a controllable weight concerning all elements there.

g089h515r806’s picture

Inline CSS and Javascript is the trend of web. Drupal need support this basic feature more friendly.

lainosantos’s picture

Inline JS/CSS is necessary for Drupal.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

firewaller’s picture

+1

off’s picture

How can I add inline js?

jamesoakley’s picture

@OFF

That's what this whole issue is about: You can't in Drupal 8 / 9.

If you outline why you need to do so, it may help others to see the importance of being able to.

frob’s picture

@JamesOkley @OFF it is absolutely possible to load inline JS.

This issue is that the code that is attached inline cannot rely on any other code or library, other than what is included in `#value`, and functions cannot be attached into drupalSettings.

/**
 * Implements hook_page_attachments().
 */
function example_page_attachments(array &$page) {
  $page['#attached']['html_head'][] = [
    [
      '#tag' => 'script',
      '#value' => 'alert("Hello!");',
    ],
    'example_inline_js',
  ];
}

But you should really open a new issue instead of commenting on a nearly 300 comment issue.

firewaller’s picture

@frob this is my current approach, however in addition to the limitation you outlined, you also cannot control where on the page the CSS/JS loads, which is problematic.

gapple’s picture

The documentation could be improved, but outlines some methods for including inline scripts that should satisfy most use cases
https://www.drupal.org/node/2274843#inline

  • Add the script directly in a Twig template
  • Place markup-generating scripts on the page with a custom block that allows full HTML markup
  • For library initialization snippets (e.g. Google Analytics), convert to a wrapper script that retrieves necessary information from drupalSettings (e.g. https://git.drupalcode.org/project/ga/blob/8.x-1.x/js/analytics.js)
  • Write out the custom script to a file, then use hook_library_info_alter() to update a library to include it
  • Insert a script element into the page head with hook_page_attachments
ambient.impact’s picture

I'd also add that if the reason for inlining a script is to alter elements right as they're made available in the DOM without waiting for DOMContentLoaded, e.g. to avoid repaints or things moving around, an alternative is to set header: true for your library and use MutationObserver to watch for the elements to become available. Note that you should be careful with this, as this makes the JavaScript render blocking, so it can increase the amount of time a user sees a blank page while it's being downloaded. You'll also want to be careful with your MutationObserver callbacks, as they can cause significant performance issues if not using performant DOM operations.

gapple’s picture

As I mentioned back in #249, this is something that contrib could handle.

and so...

The Attach Inline Module for supporting inline JS in render arrays:

$render['element'] = [
  '#attached' => [
    // Existing core functionality
    'library' => [
      'drupal/drupalSettings'
    ],
    'drupalSettings' => ['module' => $data],

    // New functionality from Attach Inline
    'js' => [
      [
        'data' => 'alert("Hi!")',
        'scope' => 'header',
      ],
    ],
  ],
];

I still think Library API + drupalSettings is the optimal solution for the majority of cases, but hopefully this is a better solution than the current workarounds.

RAFA3L’s picture

Hello, as @noc_ said in #162

I added an variable {{ footer_snippets }} in the html.html.twig file, but I can't get to print it just before the closing tag </body> and after <js-bottom-placeholder token="{{ placeholder_token|raw }}">

The only way is adding it after </body> and magically is printed above </body>, for example this works, but I don't know if is correct:

{% if page_bottom %}
      {{ page_bottom }}
{% endif %}

    <js-bottom-placeholder token="{{ placeholder_token|raw }}">

  </body>

  {{ footer_snippets }}

I need to add dynamically a script that use jquery, that's why needs to be printed at the end

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

yonailo’s picture

I have found out the same problem as the previous comment, please can someone explain how to do this ? My user case is a JavaScript inline code that uses jQuery and that comes from a content type field.

I would like to print this JavaScript code just after js-bottom-placeholder …. but it seems impossible as js-bottom-placeholders scripts seem to always be printed after my custom preprocess html variable, event if I put the twig print code AFTER the js-bottom-placeholder tag !

I have also tried to use a variable with #lazy_loading + #create_placeholder … in this case my script comes first when I inspect the DOM but the console.log messages seem to indicate that it is executed last.

Sincerely I don’t understand why all this is happening.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.