4

I'm creating an extension and using AngularJS within it. I've found a 3rd party AngularJS library that I'd like to use in my extension (in this case ngInfiniteScroll). What is the proper way to make my extension depend on this external library (and to ensure that the library is installed when the extension is installed)?

I'm pretty new to AngularJS development and CiviCRM development, so I still don't have a clear understanding of the difference between npm, webpack, bower, yarn, grunt, gulp, and browserify. Mostly I need to be pointed in the right direction.

1

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.

| improve this answer | |
  • Maybe one part that's not clear: The way I read this, and looking also at something like github.com/veda-consulting/uk.co.vedaconsulting.mosaico: For the in-app extensions screen to work, YOU, the extension developer, need to run composer install and your info.xml file needs to point to a tarball that includes the resulting files, and those downloaded files don't get committed to your extension repo tree. The above just makes it easier to make it work whether you run composer or the end user. – Demerit Feb 1 at 0:31
  • Yeah, for the in-app distribution, someone needs to run composer install on per-extension basis. It's preferable to have some kind of job (Jenkins/Gitlab CI/Github Action/etc) for making those zip files, but committing vendor gets it done too. ... Something a bit tangential to the JS/CSS, but to make the PHP side more forgiving/adaptable, I'm pretty tempted to do gist.github.com/totten/cdcf5542911052322003db7a8ba16b0a – Tim Otten Feb 1 at 2:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.