require('squash'), they'll usually get the same (think
===) squash instance. But not always.
Here are a couple times it hasn't worked out.
Let's say we're really into node-fibers with its concise coroutines and error handling. We're starting a new module, tests first, so we
npm install mocha mocha-fibers and write some failing tests. Next we
npm install fibrous to help implement our module. If we list installed fibers with
npm ls fibers we get:
├─┬ firstname.lastname@example.org │ └── email@example.com └─┬ firstname.lastname@example.org └── email@example.com
Uh oh, we've got two! This can cause some pretty weird behavior. Luckily, fibers was patched in 1.0.4 to mitigate this particular problem using global variables. If we care which version of
fibers our project uses, it's best to install it explicitly as a top level dependency, before installing
node-fibers. For example,
npm install fibers fibrous mocha-fibers yields:
├── firstname.lastname@example.org ├── email@example.com └── firstname.lastname@example.org
fibers, much better.
Recap: multiple modules with a shared dependency can get different instances of that dependency. Nothing super new there, there's even a big caveat in the docs about it.
Let's add symlinks!
Several folks have noticed that growing node apps often develop uncomfortably long require paths like
../../../../widely_shared_code. At Good Eggs, we encountered these paths requiring the json manifest of versioned assets generated by
grunt-assets-versioning. They require intense concentration to type accurately, and they sure don't help when we're moving files around.
Symlinks are a recommended workaround for these long requires. If we
ln -s ../assets.json node_modules/assets.json we can just
require('assets.json') throughout our project, no dots required! We can still use relative paths if we're in a file pretty close to the assets, perhaps in the same directory.
require('./assets.json') isn't so bad, right?
Let's audit our client side bundle for duplicates with
browserify <entrypoint>.js --list | grep assets.json . Depending on our version of Browserify we may or may not get them. Browserify can't seem to make up its mind if symlinks paths resolve to the same instance, but the authors are clear about using them for singletons:
Keep in mind that singletons are not guaranteed by either module loader (be it node or browserify). A lot of times you do get the same instance due to caching, but you shouldn't rely on that in order to enforce singleton semantics since it breaks in lots of cases.
Browserified duplicates have caused problems ranging from bloated bundle sizes to client-side app crashes due to missing configuration in one of the duplicate instances. We can avoid browserify duplication and still use symlinks for short require paths if we avoid relative requires to symlinked modules.
This is complicated
The module cache sure does not make a good service locator. I wonder what other patterns folks are using for distributing singleton instances throughout apps, especially dependencies shared between the browser and the server. Dependency injection comes to mind, but it often entails a complicated system of its own. Do you have a singleton strategy that's working well?