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
| Comment | File | Size | Author |
|---|---|---|---|
| #173 | core-allow-css-inline-with-libraries.patch | 3.63 KB | chriskinch |
| #137 | core-html-head-regions-2391025-136-do-not-test.patch | 6.43 KB | nod_ |
Comments
Comment #1
hass commentedComment #2
hass commentedComment #3
vijaycs85It 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.
Comment #4
hass commentedNope. This is a design flaw.
I need to attach inline JS or Google Analytics and Piwik will not work. (400.000 installs)
Comment #5
vijaycs85Right, I'm sorry. The change notice I reference is for settings, not for inline.
Comment #6
vijaycs85@hass, did you check these pages?
Comment #7
hass commented#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.
Comment #8
nod_This should work:
I know the
_drupal_add_html_headfunction is deprecated but the feature will stick around, core needs it.Comment #9
dawehnerThere is nothing to review here, sorry.
Comment #10
nod_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.
Comment #11
hass commentedSorry, 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.
Comment #12
catchnod_ answered this in #8.
Until that's been tried and proved not to work, please don't change the status around.
Comment #13
hass commentedIt is not working. The order of code is incorrect as html_head is on top and not after js files.
Comment #14
nod_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,#markupor 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 theinline-filetype. 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.
Comment #15
hass commentedIf 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.
Comment #16
vijaycs85I think there are two use cases in the issue summary.
Comment #17
hass commented3. add inline after JS files.
Comment #18
nod_I'd hook_preprocess_html it and add the inline js to
$variables['scripts'].Comment #19
catchStill a support request.
Comment #20
hass commentedNow it is a bug.
Comment #21
hass commented@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.
Comment #22
nod_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.
Comment #23
vijaycs85Thanks for the options @nod_. Just tried what you said in #22 and the result is here: https://www.drupal.org/project/inlinejs
Comment #24
hass commentedI 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 tohook_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.
Comment #25
donquixote commentedHas 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:
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.
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.
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)
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
(I don't really care about underscore vs camel)
Comment #26
jhodgdonThis 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.
Comment #27
hass commentedNone 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.
Comment #28
hass commentedComment #29
hass commentedComment #30
hass commentedComment #31
hass commentedComment #32
wim leersSo 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:
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:
Concern 1: hass, regarding Google Analytics
hass has helpfully summarized the key problems he encountered in the issue summary (thank you!):
Let's address these point by point:
['#attached']['html_head']markup is inserted before the scripts, and even before the styles. Seehtml.html.twig: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.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:
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?
(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:
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:
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:
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
Let's begin with Live Reload. There, they ask you to embed a snippet like this:
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 yourhtml.html.twig, plus ahook_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:
hook_library_info_alter(), easily. Completely unrelated to inline JS :)['#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 usedrupalSettings.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.
Comment #33
hass commentedCorrect order:
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.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"...
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.
Comment #34
donquixote commentedThank 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.
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.
Comment #35
donquixote commentedThis 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.
Comment #36
wim leers#33:
Nothing you wrote supports your statement . You mostly answered besides the point. And you didn't answer my two questions:
Then, your statements:
I did not even mention the word "footer". No idea what you're saying here.
No, it's not.
Sure, it can. The "cache locally" feature allows you to host it locally. Then you could have a
drupal-google-analytics.jsfile that usesdrupalSettingsto set upwindow.GoogleAnalyticsObjectand thewindow.gaalias (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 loadsga.jsjust like the snippet does.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.Another straw man. Because
CDATAalso 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.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:
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.
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:
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 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.
Comment #37
hass commentedNope. 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.
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().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.Sorry, it is the plan to do this soon.
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.
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.
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?
Comment #38
donquixote commented@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).
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.
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.
Comment #39
Jeff Burnz commentedWim, 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.
Comment #40
geerlingguy commentedEh, so I'm asking the same thing, but for CSS. For example: http://cgit.drupalcode.org/honeypot/tree/honeypot.module#n128
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).
Comment #41
geerlingguy commentedReopening 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: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.
Comment #42
geerlingguy commentedUpdating title, adding reference to #2403753: Dynamic CSS in form results in LogicException "You are not allowed to use css in #attached".
Comment #43
geerlingguy commentedNested arrays were killing me tonight, but I finally was able to use
html_headinstead ofcssfor the key, and it worked:Comment #44
hass commentedComment #45
chx commentedI 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).
Comment #46
chx commentedFurther 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.
Comment #47
catch@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.
Comment #48
hass commentedDo 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.
Comment #49
chx commentedhass, 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.
Comment #50
hass commentedDrupal 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.
Comment #51
hass commentedwindow['ga-disable-UA-XXXXXX-Y'] = true;this need to be added before GA code. This is currently not possible.I can find more examples if needed.
Comment #52
chx commentedwindow[drupal.settings.foo] = drupal.settings.bar;in your JS?Comment #53
hass commentedI'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.
Comment #54
chx commentedLet 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.
Comment #55
chx commentedOK, one week and no more problems raised. Closing it.
Comment #56
hass commentedMy issues are still not solved. Why the heck are you closing this case?
Still no answer to #38 and #39 and #51.
Comment #57
chx commented#51, put in drupal.ga.js:
The rest I can't make heads or tails of. If someone puts in a question I will answer.
Comment #58
Jeff Burnz commented#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.
Comment #59
chx commentedSo #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.
Comment #60
hass commented@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.
Comment #61
Jeff Burnz commented@hass, yes, I am working in a theme, my comments are more directed at other themers who might read this thread.
Comment #62
donquixote commented@hass Do you ever go on IRC?
To summarize:
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.
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.
Comment #63
donquixote commentedBtw, 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.
Comment #64
chx commented@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?
Comment #65
chx commentedAlso, 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.
Comment #66
hass commentedIt is a bug case you only changed it to support
Comment #67
hass commentedI'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?
Comment #68
chx commentedYou do not need inline JS like here instead
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.
Comment #69
hass commentedProvide 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.
Comment #70
chx commentedI 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:
core/tests/Drupal/Tests/Core/Asset/library_test_files/external.libraries.ymlandcore/modules/system/tests/modules/common_test/common_test.libraries.ymlon how to add external assets to libraries (the latter has weights).(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.Please show your attempts in porting and where you got stuck and we will continue.
Comment #71
hass commentedNobody 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.
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.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, pleaseMaybe 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.
Comment #72
chx commentedAh 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?Comment #73
donquixote commentedJust 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.
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.
Comment #74
chx commentedDo 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.
Comment #75
donquixote commentedIt 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.
Comment #76
donquixote commentedOne 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 solution could be:
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.
Comment #77
hass commentedThis 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.
Again - no - it is not that simple.
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:
Example 1:
Example 2:
Example 3:
Example 4:
Example 5:
Example 6:
Example 7 (metrics and dimension can be 200 each and they are dynamic per page, values are token values):
Example 8 (debugging mode)
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 requirespreprocess = FALSEalways, 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:
@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.
Comment #78
hass commentedSomething 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.
Comment #79
Jeff Burnz commentedhass, 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.
Comment #80
wim leersThere 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 withga()calls to appear, then other inline JS, then morega()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.
Comment #81
dawehnerI 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
Comment #82
hass commented@Jeff: I cannot follow what you mean with dynamic library feature.
@Wim:
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
#weighton 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?
No, I do not.
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.
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.
Comment #83
donquixote commented@hass (#77):
Comment #84
mglamanComing 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:
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.
Comment #85
damienmckennaSeeing 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.
Comment #86
damienmckennaAdding #1945262: Introduce "before" and "after" for conditional ordering in library definitions as a related issue.
Comment #87
hass commented@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.
Comment #88
hass commentedComment #89
damienmckenna@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.
Comment #90
damienmckennaPutting this back to a bug report as there are several valid use cases where this needs to be possible.
Comment #91
danny englanderI'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.phpfile:Now with Drupal 8, I attempt to set a var in my theme's .theme file within an
preprocess_htmlfunction:Then in my theme's html.html.twig file:
No doubt, I am doing something wrong but perhaps not?
Comment #92
hass commented@Wim: Can I get feedback on the mobile first issues raised in #87, please?
Comment #93
Jeff Burnz commented#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 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.
Comment #94
dom. commentedHi,
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
Comment #95
alexrayu commentedThe 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?
Comment #96
kevinquillen commentedHow 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?
Comment #97
fabianx commented#96: Please see https://www.drupal.org/node/2383115 for the change record.
Comment #98
kevinquillen commentedThanks Fabian, that really helped.
Comment #99
JayeshSolanki commentedI 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)
Comment #100
frobI 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.
Comment #101
nitesh sethia commentedDrupal_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
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
with
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.
Comment #102
hass commented@Nitesh Sethia: Before you comment on a case you should read the topic. Please remove your unrelated comment.
Comment #103
pwolanin commentedActually, 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
Comment #104
hass commentedHave you read the article I linked to and wim pointed me to? Inline css/js is a critical feature for mobile first!
Comment #105
pwolanin commented@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.
Comment #106
fabianx commentedAlso 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.
Comment #107
wim leers#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.
Comment #108
hass commented@pwolanin: See #87, please.
@Fabianx:
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.
Comment #109
hass commentedAdded links to intro
Comment #110
hass commentedextended solutions
Comment #111
pwolanin commented@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.
Comment #112
fabianx commentedCSP 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.
Comment #113
pwolanin commented@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.
Comment #114
fabianx commented#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.
Comment #115
frobThis 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.
Comment #116
hass commented@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.
Comment #117
fabianx commented#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.
Comment #118
fabianx commented#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.
Comment #119
hass commented@Fabian: 127.500.000.000 files not only 1 mio.
Comment #120
hass commentedGA 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.
Comment #121
pwolanin commented@hass - ok, from the presentation it still seems CSS in the HEAD is much more important than JS?
Comment #122
damienmckennaOut 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.
Comment #123
donquixote commented@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.
Comment #124
webchickI 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.
Comment #125
nod_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.
Comment #126
danny englander... 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.
Comment #127
donquixote commentedAs 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.
Comment #128
hass commentedYou 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.
Comment #129
alexrayu commentedYou 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.
Comment #130
Kazanir commentedI 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.
Comment #131
pwolanin commentedGiven 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.
Comment #132
fabianx commented#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.
Comment #133
hass commented@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.
Comment #134
webchickSpoke 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.
Comment #135
droplet commentedEveryone 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.
Comment #136
hass commented@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:
What are the examples of problems where I cannot migrate to drupalSettings and external files?
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:
or :
This code is NOT made for external JS files!
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.
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.Comment #137
nod_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_bottomto 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
Instead of inlining the js code, the params can be prepared and put in drupalSettings.
Comment #138
hass commentedYes we have an order issue for sure. One of the most popular comes in mind with
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
hitCallbackthan 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._nod: The code you shared does not allow real weights.
Comment #139
nod_Incomplete solutions will be given for incomplete examples. If you have functions then of course drupalSettings won't be enough.
Care to explain? As far as I know you can have
#weighton render arrays. (and the underscore in my pseudo is at the wrong end)Comment #140
hass commentedNod_: 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.
Comment #141
nod_According to D8 render pipeline,
hook_page_top/bottomrun afterhook_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,
#attachlibraries away.Did you try this before saying it won't work? They are regions sure, but they are specials ones.
Comment #142
hass commentedThat 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.
Comment #143
mikey_p commentedOne 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 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.
Comment #144
Jeff Burnz commentedI 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?
Comment #145
fabianx commented#144: If you vary by page / full url, you will need to also add a cache context:
e.g.
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.
Comment #146
kevinquillen commentedI 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:
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.
Comment #147
frobIf support for this isn't in D8 then developers and users will get around it by doing hacky things, such as:
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.
Comment #148
adam_bearThis 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.
Comment #149
wim leers#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\JsCollectionRendererservice 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
Comment #150
adam_bearIssuing http requests to multiple sources to parallelize download streams (+ possibility of script already cached) is a best practice- hardly an edge case.
Comment #151
droplet commentedAnyone 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!
Comment #152
alexrayu commentedAnd 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.
Comment #153
fabianx commented#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 :).
Comment #154
frobI have a couple of community questions about this issue.
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]
Comment #155
hass commentedComment #156
frobReferencing the doc page on attaching assets:
https://www.drupal.org/developing/api/8/assets
Changing this to a 8.1 issue.
Comment #157
johnvb commentedMy 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.
Comment #158
catchMoving to task, this is an API limitation, not a functional bug.
Comment #159
hass commentedThis 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?
Comment #160
mile23In #156 there's a link to the documentation about this: https://www.drupal.org/developing/api/8/assets
Comment #161
Jeff Burnz commented#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.
Comment #162
nod_@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 ahook_preprocess_html()works. It does depend where in the process the CSS needs to be generated.Comment #163
hass commented@nod_: Ok, sounds like a workaround for now.
Comment #165
gappleMentions 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;andhitCallback, both of which I believe have reasonable alternatives to inline scripts for implementation where they are needed.Comment #166
frobI 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.
Comment #167
droplet commented@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.
Comment #168
gappleI 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
asyncattribute 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, andga()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.
Comment #169
alexrayu commentedI need to use this functionality and can't find a work around.
https://www.google.com/cse/cse.js?cx=api_keyapi_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?
Comment #170
wim leersSee 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.
Comment #171
alexrayu commentedThank you, that solves the use case for me!
Comment #173
chriskinch commentedI 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.
Comment #174
cilefen commentedComment #176
hass commentedThere exist inline JS for the same reasons like you noted. We still need both.
Comment #178
mitrax commentedThis 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?
Comment #179
nod_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.
On top of all the caching/performance reasons, it hasn't been necessary to address specific use cases raised as problematic.
Comment #180
skaughta 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://www.w3.org/TR/css-scoping-1/ of course, is why this issue exists. caching/performance needs to be able to work with such dynamic outputs.https://css-tricks.com/saving-the-day-with-scoped-css
Comment #181
nod_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.
Comment #182
mitrax commentedFinding 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.
Comment #183
nod_See #162.
Comment #184
mitrax commented@nod_, Sorry for not mentioning that I'm developing a module that will be distributed, so changing themes is not solution neither.
Comment #185
nod_Haha, fair enough. Did you see the solution I proposed in patch #137? Might work, no?
Comment #186
mitrax commentedAs 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.
Comment #187
frobI 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?
Comment #188
mitrax commented@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.
Comment #189
hass commented@_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.
Comment #190
donquixote commented@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.
Comment #191
frob@MitraX, Can you just make your inline styles more specific? Wouldn't that override the core css that is loaded after?
Comment #192
mitrax commentedNo, @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.
Comment #193
donquixote commented@MitraX: https://developer.mozilla.org/en/docs/Web/CSS/Specificity
Maybe changing
.myclass {..}todiv.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.
Comment #194
mitrax commented@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.
Comment #195
johnny5th commented@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 itComment #196
mitrax commentedNot 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.
Comment #197
hass commented@Doquichote: i explained above that setting are not possible and why. Maybe you read all first before commenting.
Comment #198
droplet commentedIt must be something I don't know. It's interestingly these 2 way doesn't documented.
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!
Comment #199
mitrax commented@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.
Comment #200
frob@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.
Comment #201
mitrax commentedAs 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
Comment #202
mitrax commentedBesides 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.
Comment #203
nod_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.
Comment #204
donquixote commented@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.
Comment #205
mile23I'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.
Comment #206
adam_bearThis issue forced me to forget about D8... D7 wins on performance and doesn't arbitrarily limit my choices as the developer.
Comment #207
adam_bear@frob: Yes. & js.
Comment #208
mile23Review of code generally helps move things forward. There's a patch in #173.
Comment #209
mitrax commented@nod, I'm am on the topic. This is the original issue's description at the very begining, it states:
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:
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.
Comment #210
frobI 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.
Comment #211
droplet commentedI 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.
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.
Comment #212
nod_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.
Comment #213
andypostThere's
inline_templateis not it enough to output things like GA & other inline?Comment #214
hass commentedComment #215
donquixote commented@nod_ (#212):
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?
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.
Comment #216
donquixote commentedI'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.
Comment #217
hass commentedComment #218
droplet commentedAt 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.
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.
"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 $$$)
Comment #219
frobThis 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.
Let's solve this issue with JS.
Comment #220
mile23Rescoping here. Obviously, change as needed. :-)
Comment #221
hass commentedBoth 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.
Comment #222
mile23Looking forward to a patch, then.
Comment #223
chriskinch commentedI 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.
Comment #224
frob@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.
Comment #225
droplet commentedWhat 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:
Comment #226
droplet commentedThis is current drupal.org pattern: (I expected this is the minimum requirement we must achieve?)
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.
Comment #227
frob@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.
Comment #228
skaughtre: #225
a lot of people aren't aware of placing code after
</head>before<body>Comment #229
droplet commented@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)Comment #230
frobThank you droplet, I didn't know what you were trying to point out with those demonstrations. Now I know.
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.
Comment #231
droplet commented@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)Comment #232
skaught'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
Comment #233
aheimlichCorrection: It was a thing:
http://caniuse.com/#feat=style-scoped
https://github.com/whatwg/html/issues/552
https://github.com/whatwg/html/pull/1226
Comment #235
chriskinch commentedCould 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
Comment #236
hass commentedThis 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.
Comment #237
chriskinch commentedI 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.
Comment #238
progzy commented@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 > awhich get converted to:
li > aSo 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 :
and then updating on line 213:
Works for my case.
Comment #239
hass commentedSounds 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...
Comment #241
leewbutler commentedThe following pattern can be used. Note the use of twig's "raw" filter...
Comment #242
andeersg commentedI 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.
Comment #244
mitrax commentedI 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?
Comment #245
mile23The 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.'
Comment #246
jamesoakleyI 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.
Comment #247
hass commentedMatomo and Google Analytics
Comment #248
gappleThe 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.
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 (anddrupalSettings).Comment #249
gappleI'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.
Comment #250
mile23Removing '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.
Comment #251
hass commentedThis 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.
Comment #252
frobI 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
Comment #253
gapple@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 is difficult to fully respond to without a clearer example of "advanced tracking".
_paq.push()can be provided arbitrary arguments withFunction.prototype.apply(), so a small wrapper can push tracking commands with arguments provided via drupalSettings.Comment #254
hass commented@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.
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_headhas noweightfeature 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.
Can you share an example, please?
Comment #255
mile23@hass: Please file the issue on GA and link to it here, and then we can keep the 'contributed project blocker' tag.
Comment #256
hass commentedI 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.
Comment #257
adam_bearIt 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!
Comment #258
donquixote commented@Mile23 (#255)
I opened #2890438: Clean way to avoid inline js? once, but it went nowhere.
Comment #259
gappleI was unable to find any usage of
hitCallbackin google_analytics' code, nor any references in the module's issue queue.The instances where I've required using
hitCallbackhave 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.Comment #260
hass commented@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
hitCallbackinto create/before/after fields of advanced settings. All need to be possible.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.
Comment #261
frobHass, 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!
Comment #262
hass commented@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.
Comment #263
gappleInline 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.
Comment #264
frobPlease re-read my post.
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.
In my opinion this is a huge hole in the way JS and CSS is now handled in Drupal and a huge DX issue.
Comment #265
hass commentedAnother now known issue that is caused for the reason we cannot order inline in header properly.
Comment #266
frobI 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.
Comment #267
dpagini commentedThe 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.
Comment #269
anybodyYep, 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:
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.
Comment #270
g089h515r806 commentedInline CSS and Javascript is the trend of web. Drupal need support this basic feature more friendly.
Comment #271
lainosantos commentedInline JS/CSS is necessary for Drupal.
Comment #273
firewaller commented+1
Comment #274
off commentedHow can I add inline js?
Comment #275
jamesoakley@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.
Comment #276
frob@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.
But you should really open a new issue instead of commenting on a nearly 300 comment issue.
Comment #277
firewaller commented@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.
Comment #278
gappleThe 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
drupalSettings(e.g. https://git.drupalcode.org/project/ga/blob/8.x-1.x/js/analytics.js)hook_library_info_alter()to update a library to include ithook_page_attachmentsComment #279
ambient.impactI'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 setheader: truefor your library and useMutationObserverto 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 yourMutationObservercallbacks, as they can cause significant performance issues if not using performant DOM operations.Comment #280
gappleAs 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:
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.
Comment #281
RAFA3L commentedHello, as @noc_ said in #162
I added an variable
{{ footer_snippets }}in thehtml.html.twigfile, 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:I need to add dynamically a script that use jquery, that's why needs to be printed at the end
Comment #287
yonailoI 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.