Problem/Motivation
Quite a few contrib modules (for example dropzonejs, Content Browser, Select2) have an optional dependency on the Libraries module. They do this do allow the library to placed in locations other than DRUPAL_ROOT/libraries/
. For example, on drupal.org distributions wishing to including third party libraries have to use drush make. This places the library in the install profile directory.
Therefore you see a lot of code like:
/**
* Implements hook_library_info_build().
*/
function dropzonejs_library_info_build() {
$libraries = [];
if (\Drupal::moduleHandler()->moduleExists('libraries')) {
$exif_path = libraries_get_path('exif-js') . '/exif.js';
}
else {
$exif_path = DRUPAL_ROOT . '/libraries/exif-js/exif.js';
}
if ($exif_found = file_exists($exif_path)) {
One major problem with this is that libraries_get_path()
is deprecated and supposed to be removed in the Drupal 8 (!!!) version of the Libraries module. This has not come to pass but essentially all the modules that are doing something similar to the dropzonejs example above are only using the Libraries module for 1 reason and thats because it searches a defined set up of paths for libraries. This depends on two of its methods - libraries_get_path()
and libraries_get_libraries()
. Both of these are deprecated with the message:
* @deprecated Will be removed before a stable Drupal 8 release. Please use the
* new library load and managment concepts described at:
* https://www.drupal.org/node/2170763
The link shows that the Libraries module doesn't really want to continue providing this type of functionality and instead was trying to be a registry and had far more expansive D8 ideas. It doesn't appear that these have borne fruit.
Proposed resolution
There's a lot of discussion about how Drupal should support modules and themes wanting to use other frontend libraries. See #2605130: Best practices for handling external libraries in Drupal 8/9 and 10 for instance.
This issue is not trying to solve the bigger problem.
The proposal here is to search the following paths:
- PATH/TO/INSTALL_PROFILE/libraries
- libraries
- PATH/TO/SITE/libraries
when we encounter a library path like /libraries/dropzone/dist/min/dropzone.min.js
. This will allow modules to drop Libraries module integration but maintain the support for third party libraries in multiple locations.
The above search locations are the same as libraries_get_libraries()
apart from sites/all/libraries has been removed - and I think the last location found wins so PATH/TO/SITE/libraries
> libraries
> PATH/TO/INSTALL_PROFILE/libraries
Remaining tasks
User interface changes
None
API changes
None
Data model changes
None
Release notes snippet
Third party front-end libraries can now be placed in site and/or install profile-specific libraries directories, providing the same functionality as the contributed 'libraries' module.
Comment | File | Size | Author |
---|---|---|---|
#20 | 3096648-20.patch | 15.81 KB | alexpott |
#20 | 15-20-interdiff.txt | 11.25 KB | alexpott |
#15 | 3096648-15.patch | 15.63 KB | alexpott |
#15 | 13-15-interdiff.txt | 2.08 KB | alexpott |
#13 | 3096648-13.patch | 14.72 KB | alexpott |
Comments
Comment #2
alexpottSo one thought is maybe we don't need to support
@libraries
at all. We could implement dynamic library resolution for anything that starts with /libraries/ as that is the generic dumping ground. Here's a sketch of what this looks like. I've also not included sites/all/libraries because that's legacy in the libraries module so why do it here?This is a very rough sketch that works but I need to do:
Comment #3
BerdirThat's a neat idea.
It means sites can just stop installing libraries.module, and all those existing contrib modules will continue to work with a non-standard library location. And once they require an appropriate core version, they can just drop the libraries.module integration.
Existing replacements should continue to work although I'll want to explicitly test that.
Also, the additional file exists checks should be OK because that only happens on a cache miss.
should we repeat the reason for why we do this from buildByExtension()?
What about the use case of checking for the existence of the library in hook_requirements()? I suppose to we could get the library definition and check the path in there? Or should we make this API public to allow more direct access?
Comment #4
alexpottHere's a tweaked patch for #3 - thanks for the review @Berdir.
I've added tests and dependency injection. Going to look for documentation to update. Also updated issue summary to detail the current path of the patch.
I also think it is a good idea to add a requirements helper to check that a third party library is installed. Will work on that.
Comment #6
alexpottFixing some of the test fails which appear to be due to kernel tests not having an install profile.
Comment #7
alexpottSo thinking about how dropzonejs is using the libraries module. It uses it in three ways:
libraries_get_path('dropzone') . '/dist/min/dropzone.min.js';
exists$exif_path = libraries_get_path('exif-js') . '/exif.js';
exists.The patch currently covers the first use-case. But the current method added does not suit the other use cases yet (even if we made it public). I'm thinking that maybe a new service LocalExternalLibrary service might be the way to go. One that can tell you if an expected file exists in the valid libraries directory and also can help with the requirements logic. Then there's the thought the ideally maybe the LibraryDiscoveryParser could use this same service.
Still thinking on the best way of proceeding and covering the additional use-cases.
Comment #8
tstoecklerHey there ;-). Some thoughts from a Libraries API (non-)maintainer:
libraries_get_path()
was originally intended as an "extension" ofdrupal_get_path()
, i.e. it was a shim fordrupal_get_path('library', ...)
which did (and does) not exist. Since the conceptual descendant ofdrupal_get_path()
isExtensionList
it may (or may not!) make sense to have a realextension.list.library
service, etc. Again, not at all opposed to the current patch in particular, just thought I'd bring that up as general context.Comment #9
alexpottThanks for the comment @tstoeckler. I think given that core provides a libraries folder and this is functionality that many contrib modules want having it in core makes sense.
Here's a new with a libraries directory finder service so we can have the same functionality as
libraries_get_path()
Comment #10
Berdir:)
I'm not sure i understand why we require libraries/ when we then cut it off. Ah, I guess we have to cut it of somewhere?
What if we leave out "libraries" from the $directories list, then we don't have to cut off?
Sure about 9.0, wouldn't this have to be 10 now?
Comment #11
andypostComment #12
alexpottlol well it doesn't ... given #667058: Add a libraries folder with a README.txt in it to DRUPAL_ROOT but it has been the de facto standard for a very very long time.
Comment #13
alexpottFixed up #10 thanks for the review @Berdir
Comment #14
BerdirThis looks pretty good to me, I've tested it with dropzonejs and it works nicely and the simplification there is great: #3099836: Use new LibrariesDirectoryFinder
Not sure if it should be LibrariesDirectoryFinder or just LibraryFinder? It actually "finds" specific files of a library, not a directory (although that would work too I suppose), so just LibraryFinder might work too?
Also not quite sure about the contributed and custom code part.. is that even relevant? Core doesn't need it because it's allowed to include all JS code that it needs, but is that really relevant?
One use case in dropzonejs.module is an optional library. Not sure how common that is and whether or not we want to support that. We could have a flag like optional: true and if set, we'd unset the library definition. But the new code is also fairly simple although it means the module still needs to implement a hook, but it's pretty simple.
So probably this is good enough for now?
Another thing is documentation. A change record is one thing, but I'm wondering where/how we'd exactly document this new feature? We don't really have a standard for this, hook_library_info_build() mentions that you can also define it in $module.libraries.yml, \Drupal\Core\Asset\LibraryDiscoveryParser::parseLibraryInfo() mentions editor.libraries.yml by example.
Comment #15
alexpott@Berdir thanks for the review.
Re #14
I agree the name is slightly awkward but I think that's because the functionality is slightly awkward.
I've tried to improve the code comment.
Thanks for #3099836: Use new LibrariesDirectoryFinder that's really helpful to see the new API in action and it is great that it makes something simpler :)
Good point on documentation. For me this needs to be documented on \Drupal\Core\Asset\LibraryDiscoveryParser::parseLibraryInfo() as that's where we document all the keys in the *.libraries.yml files.
Comment #16
BerdirI think this looks good.
This does add a D10 deprecation, but it's only a constructor thing and I only found a single subclass that doesn't override the constructor: http://grep.xnddx.ru/search?text=extends%20LibraryDiscoveryParser. So the chances that anyone will see this are slim and definitely not with drupal-check or similar static analysis tools.
The issue summary and CR already explain quite well that this would be very useful to have already in 8.9. Many common modules optionally integrate with the libraries module only to support these dynamic paths. There are two parts to this, the main functionality here just works, so basically a site can just stop installing libraries.module and it will still work. As the patch in #3099836: Use new LibrariesDirectoryFinder shows, modules that have e.g. hook_requirements() checks need to be adjusted to use the new API and would therefore depend on 8.9, but it's not an API break.
We also have the +1 from the current libraries.module maintainer in #8. As @tstoeckler said himself, libraries.module is currently not really maintained and has a very unclear future.
Some of the relatively common D8 modules that would benefit from this:
* Slick/Blazy
* Dropzonejs
* Select2
* Shariff
* (Before 8.8 also paragraphs, but that library is now in core, so we can remove that once we require 8.8)
* There are 9 pages of results on http://grep.xnddx.ru/search?text=libraries_get_path&filename= (of course also finds the module itself, some are for php libraries and other stuff, but still, it's quite a lot)
Comment #17
catchAs a small API/site-builder facing addition I think this is probably still viable to commit to 8.9.x. The constructor deprecation is fine too.
I haven't reviewed the patch properly yet though.
Comment #18
catchOne issue with this is that we support placing different libraries in different directories on the same install - i.e. Library A in sites/SITE/libraries, library B in /libraries, and library C in profiles/PROFILE/libraries. So it is finding library directories (i.e. for one library), not the libraries directories themselves. Would LibraryDirectoryFinder be more self-documenting maybe?
Comment #19
alexpottWe're finding which libraries directory something (either a file or a directory) something is in. Hence going for LibrariesDirectoryFinder. For me LibraryDirectoryFinder would document that we're finding a directory rather than which libraries directory something is in.
I agree that the naming is not great but I think all the other suggestions so far (LibraryDirectoryFinder, LibraryFinder and LibrariesFinder) are not better. Looking at the original method that we're replacing (libraries_get_path) maybe LibrariesPathFinder? I'm not sure that's any better though.
@catch in the example you give in #18 this class is being used to find which libraries directory each library is in - so I do think that LibrariesDirectoryFinder is still appropriate.
Comment #20
alexpottDiscussed the naming with @catch and @Berdir. We settled on
LibrariesDirectoryFileFinder
because this goes well with the current description which is accurate -Finds files that are located in the supported 'libraries' directories.
I've renamed all the things accordingly.
One last decision is do we put this in Drupal 8.8.x - I would think so because it helps people prep for D9 but this is a release manager decision. And if so should we have an empty post update function to force a container rebuild to add the new service.
Comment #21
catchTo me this is a new (site-admin-facing) feature and should just go into 8.9.x
Comment #22
alexpott@catch sure and as @Berdir pointed out contrib can detect whether the service exists and use that to avoid calling deprecated code. The one thing that this means is that they can't drop the dependency on the contrib libraries module and have 8.8 compatibility. Or rather they can but then it gets super complex for them manage. I think not doing this in 8.8.x does make it harder to have modules like dropzonejs ready for Drupal 9 because no one is going to port the libraries module to Drupal 9.
Comment #23
alexpottAh so most of the D8 code has stuff like
if (\Drupal::moduleHandler()->moduleExists('libraries')) {
so there's no real dependency on the libraries module. If it is there paragraphs dropzonejs etc use it... if not then they don't.Comment #24
BerdirExactly, my plan is to basically do this in contrib modules:
if (new service exists) {
use that API
}
elseif (library module exists) {
use library module API
}
else {
use default location
}
and eventually once a module then requires 8.9, we can clean it up, and require the new service and stop integrating with library module at all. For users, the switch should be transparent, in 8.8, they need the library module if they have it in a non-default location, once they update to 8.9 they can uninstall that and then update to 9.0 without it.
I updated my patch for dropzonejs with this approach: #3099836: Use new LibrariesDirectoryFinder. Seems to be working nicely and I think this is ready, @catch agreed on slack with the name. I also updated the change record with the new name and this pattern.
Comment #26
catchSo we want users to be able to do a direct update from 8.8 to 9.x too, I can see two ways to do this:
1. Uninstall libraries module immediately prior to doing the 9.0.x update
2. Libraries could potentially do a 9.x 'port' which is just an .info.yml so that sites can do a clean uninstall after upgrading.
Opened #3110305: Drupal 8.9.x deprecation signposting against libraries module for potentially doing that stub release, and documenting options on the project page.
Fixed this on commit:
Committed 8dad334 and pushed to 9.0.x and 8.9.x. Thanks!