Announcing symdeps: Install Front-end Dependencies Where You Need Them

symdeps is an npm utility that lets you define custom install paths for your JavaScript dependencies with just a bit of configuration in your project’s package.json. It supports both symbolic and hard links, can handle individual files or entire directories, and can be set up to run automatically.

Inspiration

Recently I was excited to learn of composer’s ability to install dependencies to a custom path, and quickly wondered why this isn’t a feature in npm. The answer is probably that npm’s requirements as a package manager are defined by the needs of Node.js applications, which expect to find their dependencies in a single place: node_modules/.

Yet the fact remains that, being the go-to package management tool for JavaScript, npm is used by many developers working on a wide variety of projects, not all of them Node.js applications. For many of these projects, the node_modules/ directory simply doesn’t cut it. A Drupal project would probably need them in web/libraries/ and an Express project somewhere inside the public/ directory. If you’re using a static site generator, you’ll probably want libraries in js/vendor/ or something similar.

So far, the most popular solution I’ve seen is using Gulp/Grunt tasks to move files around. This is overkill, and we can do better. After all, where your dependencies are installed should be the concern of your dependency management configuration, not code. So I designed an alternative approach.

The Solution

First, install symdeps:

npm install --save-dev symdeps

// or for those using yarn…

yarn add --dev symdeps

With that out of the way, all you need is something like this in your package.json:

// package.json
{
"scripts": {
"postinstall": "symdeps"
},
// …
"symdeps": {
"paths": {
"web/libraries/js/vendor": [
"jquery/dist/jquery.min.js",
"mediaelement/build/mediaelement.min.js"
],
"web/libraries/css/vendor": [
"mediaelement/build/mediaelementplayer.min.css"
"mediaelement/build/mejs-controls.png",
"mediaelement/build/mejs-controls.svg"
],
}
}
}

Now every time you or your teammates run npm install, the following files will be symlinked to their appropriate source in ./node_modules/:

  • web/libraries/js/vendor/jquery.min.js
  • web/libraries/js/vendor/mediaelement.min.js
  • web/libraries/css/vendor/mediaelementplayer.min.css
  • web/libraries/css/vendor/mejs-controls.png
  • web/libraries/css/vendor/mejs-controls.svg

If you need hard links (say if you’re creating a deployment artifact and need actual file pointers instead of symbolic links), add the --hard flag to the command or set "hard": true in the symdeps settings, like so:

// package.json
{
"scripts": {
"postinstall": "symdeps --hard"
},
// or…
"symdeps": {
"hard": true,
"paths": { /* … */ }
}
}

This saves you the time and, let’s face it, crud of setting up, maintaining, and running Gulp/Grunt tasks designed exclusively to move files around. It’s also one less task that needs to run on every build.


I hope you find this useful. Bug reports, feature requests, and pull requests are welcome on GitHub!