A Guide to Loading External JavaScript in Drupal
In a recent internal discussion, the topic of incorporating an external JavaScript dependency into a Drupal project came up. To the surprise of many, we found that this common task still inspired quite a bit of discussion. How could that possibly be? As is often the case in the world of Drupal, there are a number of valid approaches with subtle but relevant differences. The approach to add a JavaScript library can change if you view the task as front end versus back end.
Let's explore the different ways you can include a third-party JavaScript library into your next build — either as a dependency of your theme, custom module, or overall project.
Assumptions
Before we dive into specifics, let’s first talk about a few assumptions that we had as a team.
First off, all of these approaches assume that our preference is to have JavaScript as a local dependency rather than externally hosted. This allows greater control over items like aggregation and would also allow all of the assets in your project to be hosted on the same Content Delivery Network (CDN).
Secondly, our preference is to use package managers like Composer and NPM to easily import external dependencies. Given how common Composer is on Drupal projects and NPM is on front end projects, this isn’t a very controversial assumption, but it does dictate a number of things related to the approach.
JavaScript That is a Dependency of Your Theme
If your JavaScript is a dependency of your theme, the specifics will likely vary a bit based on your front-end tooling and workflow. But at the highest level, you’ll be adding your dependency using your preferred package manager. We typically use NPM on projects, but if you’re using Yarn or another tool the process will be similar. If for example, your dependency was the Inline SVG package, you’d run the following in the same location as your existing package.json
file:
npm install inline-svg
In this case, Inline SVG is a production dependency, so be sure not to use the --save-dev
option when installing in order to ensure that this dependency will be available for production builds.
Now that your dependency is available, how you incorporate it into your project will vary based on your workflow. We’re increasingly using Webpack as part of our front end workflow. If you’re doing the same, you’d most likely want to import this dependency within your JavaScript so that this can be part of your main bundle or the appropriate code split portion of your bundle. Those bundles are most likely already incorporated into libraries that are part of your theme. The import statement would look something like this:
import inlineSVG from "inline-svg";
If you’re not using a bundler and are instead using something like Gulp (or the less likely scenario of no task runner at all), you’ll likely want to create libraries for these dependencies. The tricky part here can be making the necessary files easily available to your theme. When faced with this problem in the past, I’ve found the vendor-copy utility helpful. Vendor copy allows you to copy client-side dependencies to the folder of your choosing.
Once the files are somewhere that your theme can access, you can follow the standard approach to including JavaScript assets in a library in your theme.
JavaScript that is a Dependency of A Project
If your JavaScript is a dependency of your overall Drupal project, the approach will be a little bit different. Since you’re managing PHP dependencies with Composer, it would be ideal to manage your JavaScript dependencies with Composer as well. Thankfully, Asset Packagist allows you to do exactly that. If you’re using the new recommended project composer template available starting in Drupal Core 8.8, you’ll need to make a few adjustments to your composer configuration as outlined in Drupal’s Composer documentation.
Add the Composer Installers Extender PHP package to your project's root composer.json
file, by running the following command:
composer require oomphinc/composer-installers-extender
Add Asset Packagist to the "repositories" section of your project's root composer.json
.
(Note: the screenshots below illustrate the difference you would see after making these changes to the default composer.json
created by drupal/recommended-project)
Ensure that NPM and Bower assets are registered as new "installer-types" and, in addition to type:drupal-library
, they are registered in "installer-paths" to be installed into Drupal's /libraries folder.
You may now require libraries from NPM or Bower via Composer on the command line by running something like:
composer require npm-asset/slick-carousel
With the settings above, you’ll end up with a slick-carousel
folder in your web/libraries directory containing the assets for the slick-carousel
NPM package. Since you now can predict the location of these assets, you can create libraries in Drupal that can be used to load your JavaScript dependencies.
If your dependency doesn’t exist on Asset Packagist, there are still a few ways that you can configure Composer to manage your dependency. If the dependency has a repository that contains a composer.json
file (or if you could fork the dependency and add one), then you should be able to load the package as a VCS repository using Composer. If the dependency can’t include a composer.json
file, you may be able to adapt this recipe on managing CKEditor plugins with Composer to meet your needs.
JavaScript that is a Dependency of A Custom Module
It is also possible to add your dependency to a composer.json
file included within a custom module rather than the root level composer.json
file. This has the advantage of allowing you to fully self-encapsulate your module, including the necessary JavaScript dependencies. If you’re publishing your module and using it across multiple projects, this is the ideal approach.
The story is a little different if the custom module is specific to a single project. Current best practices require manually adding each custom module to the repositories section of your project’s root composer.json
file, which can be tedious if you have a number of custom modules. As a result, we’ve found that we are more likely to add these dependencies at the level of the Drupal project. With Drupal continuing to evolve its Composer support, it will hopefully become easier to inherit dependencies from custom modules and less necessary to individually add dependencies at the project root level.
If All Else Fails…
If none of the package manager based solutions are practical for your use case, you still have a few other options. Let's say there is an externally-hosted version of the dependency on a CDN. You can add this as an external library in your module or theme’s libraries.yml file like this:
angular.angularjs:
js:
https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true }
Finally, if an externally hosted version of the library isn’t available, you could also take the brute force method. Download the dependency, then package it with your module or theme. This makes it a little more difficult for the dependency to be quickly updated, but at least it gets the job done.
Common Gotchas
While it is great to have the option to add JavaScript dependencies via Composer with something like Asset Packagist, this approach does present some possible challenges. Since you’re now potentially updating JavaScript dependencies along with PHP dependencies, you’ll need to make sure that this is correctly coordinated across the team so there are no surprises on the front end. If for example, your work depends on a specific version of Slick Slider, you’ll need to make sure to pin the specific version of this library in your composer.json
. You may also need to adjust your workflow to ensure that your front-end developers have the opportunity to control or at least review any updates to these dependencies.
For cases where you’re managing dependencies of your theme using NPM, it is also important to consider if these dependencies are specific to a single theme, or shared by multiple themes. If they are shared by multiple themes, you may want to consider making some of your JavaScript libraries a dependency of your base theme. This allows you to standardize on a particular version of a dependency and more easily update libraries across multiple themes. On the flip side, you can manage different versions at the individual theme level to give you more control. However, this will add complexity to your theme inheritance and require more effort to manage.
Happy JavaScripting!
The front-end landscape continues to grow and evolve faster than ever before. Every day there are more and more tools in the JavaScript ecosystem that can help us build amazing user experiences with less effort. Hopefully, better understanding the approaches to add external JavaScript dependencies to your project will make it easier to take advantage of these tools and build amazing front end experiences using Drupal. We can’t wait to see what you build.