Recently I’ve been experiencing some pain points when trying to share private package between my Meteor projects.
Suppose you have two or more Meteor projects that share similar styles, components and functionality. Wouldn’t it be nice to bundle those shared bits into packages and use them across all of your projects?
The Problem With Packages
Packages are the solution! Well, partially…
Many articles have been written on the ease-of-use and power of Meteor’s packaging system. Unfortunately, when we’re writing closed source software, we’re not in the position to publish our private packages Meteor’s public package ecosystem. Our need for privacy means that we need to share our packages in a different way.
Git to the Rescue
My first attempt to solve this problem was to keep my private packages within private Git repositories. When an application needed one of these shared packages, I would clone the package into the project’s packages folder, and then add the package using the familiar meteor add
command. Updates to the package could be made by manually running a git pull
from within the package’s directory (goodbye meteor update
).
cd packages
git clone https://github.com/pcorey/private-package.git
meteor add private-package
cd private-package
git pull
Finally, track the new package in the base project and commit the changes:
git add .
git commit -m "Added private package to project!"
This will work until a second developer checks out a fresh copy of the project. They’ll quickly notice that the packages/private-package
is an empty directory! Where did our package go?
Unfortunately, Git noticed that the the packages/private-package
directory was actually another Git repository, so it added the directory as an unmapped submodule, not a normal folder. This means that the base project isn’t concerned with tracking the contents of the packages/private-package
directory, assuming that the submodule will handle that itself.
Git Submodules
Git submodules are the “accepted” standard for dealing with this kind of repository nesting. Git expects you to add and map these submodules through a special set of commands. Using our current example, to add private-package
to our Meteor project we would:
git submodule add https://github.com/pcorey/private-package.git packages/private-package
meteor add private-package
This will pull the package down from its remote repository and set up the submodule mapping within Git. At this point, we can once again commit the changes to our base project. Another developer checking out a fresh copy of the project will now look at packages/private-package
and see… an empty directory? Still?
One of the unfortunate subtleties of Git submodules is that they need to be initialized and updated on fresh checkouts:
git submodule init
git submodule update
Only then will the remote contents of each submodule be pulled into the project.
This means that the developer doing the checkout must be aware that submodules are being used by the project. This isn’t always the case, and often adds an unfortunate complexity when trying to introduce someone to a codebase.
Unfortunately, submodules have their fair share of quirks and problems.
“Fake” Submodules
My preferred method of sharing private Meteor packages between projects is a combination of the above two techniques, sometimes referred to as “fake” git submodules.
I begin by cloning my package into the packages
folder, as before:
cd packages
git clone https://github.com/pcorey/private-package.git
meteor add private-package
There is a subtle step in how the package is added to my base project:
git add private-package/
The key here is the “/
” at the end of the private-package path. Run a git status
and you’ll notice that all of the private package’s files are now being tracked by the project. Additionally, if we cd
into private-package
and run a git status
, we’ll see that the private-package
is still operating under it’s own independent Git environment. The slash causes Git to treat this sub-repository as a normal directory and happily tracks all files within it without our base project!
Commit the changes to the base project and push them. Now, a new developer checking out a clean copy of the project will see that the private-package
directory contains the contents of the package, as expected.
The only downside of this technique is that private-package
in the fresh checkout no longer maintains a remote link to its private package repository. If changes need to be made in this package and pushed to the private package’s repository, the remote would have to be re-established:
git init
git remote add https://github.com/pcorey/private-package.git
git checkout master --force
Final Thoughts
My final thoughts are that this is a mess. I hope that the Meteor team adds some mechanism for adding and updating packages from private sources, rather than exclusively from its public package ecosystem. Maybe one day it will be as easy as meteor add
and meteor update
, but for now, these are the tools we have to work with.