An approach: composer-downloads-plugin
civicrm-core
now uses the https://github.com/civicrm/composer-downloads-plugin for Javascript and CSS libraries. You can use the same technique in an extension. For example, suppose you have an extension civinfinite
with a composer.json
file which declares:
{
"name": "example/civinfinite",
"require": {
"civicrm/composer-downloads-plugin": "~2.1"
},
"extra": {
"downloads": {
"ngInfiniteScroll": {
"url": "https://github.com/sroze/ngInfiniteScroll/archive/1.3.4.zip",
"path": "extern/ngInfiniteScroll",
"ignore": ["test"]
}
}
}
}
The zip
file will be downloaded and extracted into your extension's folder (under extern/ngInfiniteScroll
).
For example, suppose that you install civinfinite
on a Civi-D7 site under:
sites/default/civicrm/ext/civinfinite
If you run composer install
, then the assets will be at:
sites/default/civicrm/ext/civinfinite/extern/ngInfiniteScroll
At runtime, you can determine the URL as:
Civi::resources()->getUrl('civinfinite', 'extern/ngInfiniteScroll/src/infinite-scroll.js');
Similarly, if you're registering an Angular module for use in Civi (hook_civirm_angularModules
or *.ang.php
), then the registration can reference that path:
[
'ext' => 'civinfinite',
'basePages' => [],
'js' => ['extern/ngInfiniteScroll/src/infinite-scroll.js'],
];
Critical analysis
There are many different techniques for managing JS/CSS assets in PHP projects -- D7 modules have libraries
; Backdrop and WordPress modules tend to embed assets within modules; composer
has various add-ons like installer-paths
, asset-packagist
, composer-npm-bridge
, composer-asset-plugin
, etc. Each of these has strengths and weaknesses.
I would submit that the relative strength of this technique is that it is easy for both developers and site-builders to reason about the behavior. (Tangentially, it's also faster than other composer-based techniques because it has no affect on the size of the dependency graph. But the main purpose is to make it easier to reason.)
For example: Consider that there are different ways in which people may deploy an extension:
- One may download an extension as a
*.zip
file with all assets bundled in. (This is what happens when using the in-app extension UI or cv dl
or Extension
API.) One may not be running composer
at all.
- One may clone the extension's git repo and run
composer install
.
- One may have a
composer.json
in a site-root (and some open-ended mix of dependencies, plugins, configuration options), and then you add the extension as a dependency.
As a developer, it's hard to predict which mix of techniques are being used by downstream site-builders. In the composer-downloads-plugin
approach, you can rely on the JS/CSS assets always being in the same place (relative to your code).
As a site-builder, this approach does not require you to add (or change/update) any "repositories" or "plugins" or configuration-options in a composer root-project. You don't even need to have a composer root-project. (You probably should for other reasons... but the extension and its assets aren't going to force you into it.) If a developer changes some small detail of the assets in their package, then it's not your concern.
This approach complies with a simple, pre-existing contract:
- The developer decides what code goes inside their extension.
- The site-builder decides which extensions to get - and where to put them.
A developer is free to use composer-downloads-plugin
without requiring any changes to that basic contract.