Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
Stealing this idea from dawehner!
We are now using libraries for all CSS and JS. To add any of these libraries in a theme, they either have to be added site-wide or a theme needs to use a preprocess function to specify when they get added. It would be great if a themer had the ability to add a library directly from a template, to avoid creating preprocess functions (and using php.)
Proposed resolution
Add a Twig simple function.
{# In a Twig template we attach a library of our choosing. #}
{{ attach_library('mytheme/library1') }}
Remaining tasks
Discuss.
Agree.
Do.
Post change record.
Rejoice.
API changes
Adding a function would be an API addition.
Beta phase evaluation
Issue category | This is a feature because it is an api addition. |
---|---|
Issue priority | Normal because it is nice to have, but isn't preventing anything form working. |
Unfrozen changes | Unfrozen because it is an addition that should really only affect templates, which are not frozen. |
Prioritized changes | The main goal of this issue is to improve the themer experience. |
Disruption | This should not be very disruptive because it is an addition, so there are no backwards compatibility concerns. Besides the addition the only noticed change might be in core template files. |
Comment | File | Size | Author |
---|---|---|---|
#85 | interdiff-59to85.txt | 420 bytes | davidhernandez |
#85 | add_the_ability_to-2398331-85.patch | 8.77 KB | davidhernandez |
#79 | interdiff.txt | 420 bytes | nod_ |
#79 | core-twig-attach_library-2398331-79.patch | 8.34 KB | nod_ |
Comments
Comment #1
davidhernandezJust getting started making a twig simple function.
Comment #2
cilefen CreditAttribution: cilefen commentedAdded \Drupal::service('template.attachment.manager') with getAttachments and addAttachments.
Comment #3
davidhernandezI'm going to do some more tweaking but this actually functions using addLib in a template. For example,
{{ addLib('bartik/maintenance_page) }}
Comment #4
davidhernandezNot sure why it didn't add my file.
Comment #5
davidhernandezChanging that from a service to a static class.
Comment #7
davidhernandezThat was dumb. I diffed my previous commit instead of versus head.
Comment #8
davidhernandezComment #9
cilefen CreditAttribution: cilefen commentedComment #10
cilefen CreditAttribution: cilefen commentedComment #12
cilefen CreditAttribution: cilefen commentedComment #13
dawehnerIt feels a bit odd
I kinda hate requiring a shortname, ... note: do we really use camel casing for those methods?
Comment #14
cilefen CreditAttribution: cilefen commentedThis is the result of a conversation with @dawehner on IRC but it doesn't work yet.
Note that to use this form, there are no parameters to the twig function:
Comment #16
star-szrThat seems rather unusual, or in other words it feels magical. Why not pass in the libraries array? Or what is the reasoning for special casing it like this?
Comment #17
cilefen CreditAttribution: cilefen commented@Cottser any sufficiently advanced technology will seem like magic. Or, I'm groping in the dark. It's one or the other.
Comment #18
davidhernandezQuestion about the function name. Is there a standard convention we follow? Is camel casing only used for class methods?
Comment #19
cilefen CreditAttribution: cilefen commentedThis is #12 but changing the command name to 'add_libraries' which matches the pattern of the other extensions.
I feel this needs a unit test of the Twig extension itself in addition to the unit tests on AttachmentManager.
Comment #20
cilefen CreditAttribution: cilefen commentedIt may be the other extensions don't have tests. For example, I grepped the codebase for 'url_from_path' and found nothing besides its definition in TwigExtension.
Comment #21
cilefen CreditAttribution: cilefen commentedA WebTest for this would go in Drupal\system\Tests\Theme.
Comment #22
joelpittetIS update, match function name to latest patch.
Comment #23
joelpittet@cilefen regarding 'url_from_path', it's a throw back for the people who aren't using routes but are used to D7 paths. It may not survive as it seems we are aiming for routes only... but I don't think that is possible in reality so we'll see what happens there.
If I remember right, it was to placate my resistance to change:P
Comment #24
nod_Supporting this. Twig can do it, people will expect something like this to work in Drupal templates.
I'm not up to date on twig so do we need to have a set and add_libraries() call? can't we have both at the same time, like:
Just asking, the proposed solution in IS looks good enough to me.
Comment #25
davidhernandez@nod_, no, the set should optional. In the example, it is just a cleaner (easier to read) way to send the array.
Comment #26
joelpittet@nod_ yes, you can have it like that. Personally I prefer it that way. Though a few think it's easier to read with the set, so we've done it that way fairly consistently that way with adding css classes on attributes for example.
Comment #27
cilefen CreditAttribution: cilefen commentedThere have been changes to how assets are managed recently, so this patch is just the twig function but it doesn't do anything.
Comment #28
Wim LeersComment #29
Wim LeersRelated: #2451411-9: Add libraries-override to themes' *.info.yml.
Comment #30
Wim LeersI went with
instead. This feels more natural. One library per call. More than one call (and thus more than one asset library) per template signals poor library structure. If multiple libraries need to be loaded, then it's 99.9% certain you should be using library dependencies!
I converted Bartik's
messages
template to attach thebartik/messages
library (which I also split off frombartik/global-styling
— meaning that that CSS is now only loaded when necessary).Does this mean we now want to remove the ability to set
$variables['#attached']
in preprocess functions? I.e. remove the bottom if-test from this bit of code inThemeManager
:Or is it worth having both supported?
Comment #32
davidhernandezThanks for working on this! :D
I'm not sure 'attached' would feel more natural for themers, but I leave that up for debate. I'm not going to bikeshed on the name.
I don't think so. I'd want to have both options available, because you don't want to have to have a template just to attach the library. I don't think attaching from the template is the "better" way to do it, just that we want to give themers the ability so they don't need to know the PHP way to do it.
I'm fine with one library per call.
Comment #35
cilefen CreditAttribution: cilefen commented@davidhernandez I think 'attach' is the word to use because it matches the terminology elsewhere.
Comment #36
Wim LeersI forgot to update the test coverage. Easy fixes.
Comment #37
Wim LeersMy reasoning exactly.
Comment #38
Fabianx CreditAttribution: Fabianx commentedThis is naming, but I think attach_library() is better, that also matches the internal name better.
The rest looks good to me :).
Comment #39
Wim Leersattach_library()
suggests other things may be attached. I don't think we want templates attaching feeds, headers or<head>
elements. If we agree on that, then the conclusion is that we only want one thing to be attachable by templates: libraries. Then IMOattach()
is better thanattach_library()
.(That was my reasoning. Should've posted that right away.)
Comment #40
nod_The function having only one parameter, I agree. Dependencies and all that, if more are needed several calls to the function is not the end of the world.
We have to keep #attached in preprocess, module can/will use it and they shouldn't need to have users adding stuff to their templates to do so.
I agree with Fabianx about the naming though. It's arbitrary to say "attach" means "attach libraries only" in the context of twig, it is also counter-intuitive compared to the rest of the API. To me it's the same discussion we had optimized/minified in library declaration:
attach_library
is more explicit and can't be misunderstood without a lot of bad faith, very easy to thinkattach
could means something else. As you said, we want to make sure people understand only libraries can be attached in templates. On top of that, the PHP method is calledattachLibrary
, let's keep things consistent.Comment #41
Wim LeersSolid reasoning — thanks! Rerolled accordingly.
Comment #42
davidhernandezI agree with nod_. "attach()" sounds more generic.
Comment #43
davidhernandezComment #44
Fabianx CreditAttribution: Fabianx commentedThis is not a feature request, but a task.
This would be RTBC, but still needs tests.
And its nice to get the messages into its own library as a good example - but need bartik approval for that by @emmamaria. Having @Cottser and/or @joelpittet look over this would be good, too.
This needs a beta evaluation and possibly a change record or change record update.
Comment #45
Fabianx CreditAttribution: Fabianx commentedComment #46
davidhernandezDefinitely would need a change record.
Comment #47
lauriiiwill do tests for this
Comment #48
sqndr CreditAttribution: sqndr commentedBeen playing around with this, but I couldn't get it to work. It's probably just too late. I'll give it another go tomorrow. As mentioned (on Twitter), I like this idea!
Comment #49
star-szrSmall comment: I fear people won't know what TX means looking at this.
Not putting needs work because of that, just because of the tests :)
Edit: it definitely works for me, @sqndr make sure you clear caches after applying the patch?
Comment #50
joelpittetI'm a fan! RTBC++ after the doc cleanup.
Comment #51
joelpittetHere's a change record it could use a few eyes and touchups if you have them.
https://www.drupal.org/node/2456753
Comment #52
sqndr CreditAttribution: sqndr commentedAgreed. Also got it working ;) Again: I like this!
Comment #53
sqndr CreditAttribution: sqndr commentedWhat I tried yesterday was:
- Copy file from
html.html.twig
from core/system/templates into own theme- Add
{{ attach_library('panda/kitten') }}
(same line as screencast above)Result:
- No
kitten.css
is added to the pageRemarks:
- As mentioned, cleared the cache. Works for
page.twig.html
.Something I did wrong?
Comment #54
nod_One thing I wonder is if the library added this way is available in preprocess function?
How would we go about getting rid of a library added from a template, is it something that should even be possible?
Comment #55
cilefen CreditAttribution: cilefen commentedI updated the change record.
Comment #56
davidhernandezFrom Wim via Twitter, "html template is special: it must render the assets, so it can't add more."
@nid_, I don't know if manipulating it from preprocess is possible, but it doesn't seem like we need it to be possible. The template is at the highest level (or is it lowest?) so adding something there should be considered the end of the line. If someone needs more complex logic, the library probably shouldn't be added at the template level.
Also, agreeing about the "TX" line. The whole line is probably superfluous, but we can at least spell it out if it is kept, or make it more detailed.
Change record looks good.
Comment #57
Wim Leers@sqndr Please stop embedding multi-megabyte GIFs. They make loading d.o issues terribly, terribly slow. It's fine to attach them, just don't embed them. Or, alternatively, just attach screencasts. They'll be smaller too!
Comment #58
Wim LeersAddressing remaining feedback.
Comment #59
Wim LeersThe same problem exists for preprocess functions. The only way to remove this is to write a subtheme and override the parent theme's preprocess function.
Similarly for templates: you'd have to override the template in a subtheme. Or, if the
attach_library()
is in a Twig Block, then you can choose to extend the base theme's template and only override that block.The problem described in #53 and my Twitter response cited in #56 need some more explaining probably.
While rendering a page, we're rendering many render arrays, theme functions and templates. The attached libraries bubble up as expected. But then we reach a point where we've rendered everything in the page except the
<html>
itself: we've renderedpage.html.twig
(which represents the contents of the<body>
) and everything it contains. However, at some point, we need to render the<html>
tag, which contains<body>
(and thus the renderedpage.html.twig
template). But it also contains the<head>
, which must list all assets to be loaded.And that is where things get tricky. The
html.html.twig
template's preprocess function (template_preprocess_html()
) generates thestyles
,scripts
andscripts_bottom
variables, which thehtml.html.twig
template prints.The
template_preprocess_html()
preprocess function preprocesses variables for the template, and then the template is handled. So anyasset_library()
call in thehtml.html.twig
template simply is too late.Therefore
THEME_preprocess_html()
andattach_library()
inhtml.html.twig
are impossible to support.Comment #60
nod_That sounds reasonable to me, no issue left for me, Thanks!
Comment #61
sqndr CreditAttribution: sqndr commentedComment #62
sqndr CreditAttribution: sqndr commentedSorry 'bout the gifs. Still looks good to me.
Comment #63
sqndr CreditAttribution: sqndr commentedComment #64
Fabianx CreditAttribution: Fabianx commentedRTBC, looks great!
Comment #65
alexpottI wonder how this
I wonder how this plays out with css aggregation?
Comment #66
cilefen CreditAttribution: cilefen commentedSomewhat related - I am merging those dependency setter methods. #2416831: Add an active_theme twig function
Comment #67
Fabianx CreditAttribution: Fabianx commented#65: The same as if added in a theme preprocess function via #attached, which we support.
It only does not work, the same as for preprocess_html() for html.html.twig, but that is as CSS is already aggregated at that point.
Comment #68
Wim Leers#65: like #67 says: it's exactly the same as HEAD.
(In Drupal 8.1.0, we should leverage the asset libraries dependency tree that we now finally have in D8 to group assets in a smarter way. But that's completely independent and out of scope for this issue.)
Comment #69
alexpottYes we can cause extra aggregations by using #attached in HEAD - but this patch changes things in bartik to cause extra aggregations on every page that has a message. Given that often when adding a message people are doing a value that adds value to your site does this make sense?
Comment #70
alexpottLet's just revert the changes to bartik and then I think we're fine to proceed here. The beta evaluation needs an update because it calls this a feature yet the issue is a task.
Comment #71
Wim LeersI think he change to Bartik is a net improvement: messages are shown *rarely*, and almost only after a POST, so it's logical for messages' CSS to be excluded from the global styling (which is loaded for every page) of a theme.
Comment #72
lauriiiI don't think there is a real reason to exclude messages.css by default because the improvement is so small. I think its more about what kind of example we want to give in the core theme for this kind of situation. What I think is that messages-block is one of the best examples to demonstrate how people should use libraries (only load attachments when they are necessary) so probably it would probably make sense to include the attachment in the template even though in this example the improvement made to rest of the pages is minimal.
Comment #73
Wim LeersYes, the net win is minimal, but the principle is right: only load assets when necessary, and definitely those that are needed only a small fraction of the time.
So, yes, it's more about the example than the performance gain. (That's also why there's no perf-related tag on this issue.)
Tentatively re-RTBC'ing.
Comment #74
alexpott@Wim Leers but is there not a tension here? Downloading fresh aggregated css assets because they include different libraries has a significant cost to it - right?
Comment #75
catchThis got discussed a lot on #769226: Optimize JS/CSS aggregation for front-end performance and DX.
In this case messages.css would be a case for a file that never gets aggregated. So you get the extra HTTP request/cache miss when messages are shown, but not the cost of generating and downloading the new aggregate.
Comment #76
catchBack to CNR - I think we should consider setting preprocess: false on the messages.css file, that'd only be bad for a site that is showing a permanent drupal_set_message() for some reason (which I have seen, but is unusual). Otherwise it gets rid of the main trade-off here between showing it everywhere or downloading duplicate files.
Comment #77
Fabianx CreditAttribution: Fabianx commented+1 to setting preprocess: false here.
Comment #78
catchComment #79
nod_As is, when CSS aggregation is on, there is a new aggregate file containing only message.css, it's not invalidating existing aggregates, just add one more file to download.
How stuff is aggregated could use an improvement but anyway, preprocess if off for messages.css.
Comment #80
Fabianx CreditAttribution: Fabianx commentedBack to RTBC, very nice and concerns addressed!
Comment #81
Wim Leers+1 — makes sense :)
Comment #82
catchWow #1924522: Remove separate CSS_AGGREGATE_THEME aggregate file never actually made it in, @nod_ does that explain #79 or is something else happening?
Comment #84
davidhernandez#79 is missing the template file used for the test.
twig_theme_test.attach_library.html.twig
Comment #85
davidhernandezComment #86
nod_Thanks, looks like I'm a bit rusty :)
Comment #87
alexpottOkay my concerns have been addressed - thanks everyone! Committed a2efc8a and pushed to 8.0.x. Thanks!
Thanks for adding the beta evaluation to the issue summary.
Comment #89
davidhernandezI published the change record. Rejoice!
Comment #90
nod_Almost feel like we should fill follow-ups to move a bunch of stuff in templates now that we can. Like vertical tabs, progress bar, dropbutton, details polyfill, etc.
Comment #91
davidhernandezIt might be an even better thing for core to do because if people copy the template they can just remove the line if they don't want the library. No need to worry about -remove -override stuff. But it's still case by case. Bartik and Seven certainly have a few places where this can be changed. Maybe we'll start there and not make a project of it all.
Comment #92
lauriiiI think this issue broke some peoples Twig Extensions because now you are forced to add
arguments: ['@renderer']
in services.yml. Btw noticed that exceptions doesn't work very well on situations like this..Comment #93
star-szr@lauriii Those people should be extending the vendor \Twig_Extension, not core's. See also #2408013-88: Adding Assertions to Drupal - Test Tools. and the Twig extension part of https://www.drupal.org/node/1831138.
Comment #95
calbasiHi,
If I'm not wrong, I should be able to load a library from any twig template of my theme. But I can not do it from a template like page--imprescindible--%.html.twig, that in the other hand is loading well its html markup.
When I try to load the same library from html.html.twig its OK. I use:
{{ attach_library('basic/imprescindibles_section') }}
Well, is it working as desired? Reading here and there it seems I should be able to load a library from any theme twig template, but drupal documentation suggest other way to add a library for certain pages:
https://www.drupal.org/docs/8/theming-drupal-8/adding-stylesheets-css-an... (look at "Attaching a library to a subset of pages" section)
The situation is the opposite you can read here: https://sqndr.github.io/d8-theming-guide/libraries/libraries-and-twig.html and not the described here: https://medium.com/@ToddZebert/loading-and-using-javascript-in-drupal-8-...
Then, I wonder if I have found a bug? or I can only use attach_library from html.html.twig? If the second is true, then I'd edit drupal.org documentation trying to help with all this information mess.
Comment #96
calbasiIn fact, at drupal documentation you can read:
If that's true, I should be able to load my library from page--imprescindible--%.html.twig
Comment #97
calbasiJust resetting issue status, and related stuff
Comment #98
davidhernandezThis is a closed issue. If you think you've found a bug, please open a new issue.
Comment #99
Wim Leers#3050386: Allow loading CSS and JavaScript directly from templates is proposing to take this even further.