Problem:
It's often useful to include compiled front-end code in npm modules (eg: CSS transpiled from SCSS). However, it's suboptimal to commit compiled code to your repository because you end up with bloated diffs in your commit history and a higher chance of merge conflicts. You'll also end up with a much larger overall filesize for your repo, which will slow down pushes and pulls.
Antipattern:
Commit compiled code – often to a folder such as dist
.
A Better Solution:
Only include compiled code in the bundle you publish to npm. In this manner, consumers get your compiled code while library co-developers do not.
Basics
In order for this all to work properly you'll need to do a few things:
First, add the folder(s) containing your compiled files to .gitignore
. This will ensure no one accidentally commits compiled code.
Second, add a .npmignore
file that contains the same excluded files as .gitignore
, but does not include references to your compiled files. .npmignore
takes complete precedence over .gitignore
, and you want npm to include your compiled files.
If you can get by with only a few directories in your bundle, you can take a less repetitive approach by ignoring a wildcard patttern (/*
) and overriding with just the files and folders you do want included by prepending them with a !
(see example below).
Example: A more D.R.Y. .npmignore
:
/*
!dest/
!src/
Level Up With Versioning Hooks
Running npm version
is a convenient way to bump your module's version number (assuming you use semver – which you should!). It also creates an associated tag and commit. Additionally, npm version
can trigger three specially named npm scripts: preversion
, version
, and postversion
during its execution.
Excerpt from npm's version command documentation:
If
preversion
,version
, orpostversion
are in the scripts property of the package.json, they will be executed as part of runningnpm version
. The exact order of execution is as follows:
- Check to make sure the git working directory is clean before we get started. Your scripts may add files to the commit in future steps. This step is skipped if the
--force
flag is set.- Run the
preversion
script. These scripts have access to the old version inpackage.json
. A typical use would be running your full test suite before deploying. Any files you want added to the commit should be explicitly added usinggit add
.- Bump version in
package.json
as requested (patch
,minor
,major
, etc).- Run the
version
script. These scripts have access to the new version inpackage.json
(so they can incorporate it into file headers in generated files for example). Again, scripts should explicitly add generated files to the commit usinggit add
.- Commit and tag.
- Run the
postversion
script. Use it to clean up the file system or automatically push the commit and/or tag.
Example:
All scripts from package.json
:
"preversion": "git checkout master && git pull origin master && npm test"
This will ensure you're on the correct branch for creating your tag (probably master
), check that you have the latest code, and finally that all tests pass as expected.
"version": "npm run build"
Run a local build (assuming you have a npm script called build
), which might for example create a dist
folder containing compiled code. Since this step runs before your npm package is assembled you will end up with your compiled code available for bundling.
"postversion": "npm publish && git push origin master --tags"
Publish your new version to npm, push the bumped package.json
to master
, and push the newly created tag to the origin
remote.
Testing
While you're setting up your new workflow, it's nice to be able to see what will end up in your bundled package without actually publishing it. Currently, there's a ticket open for adding a dry run option to npm publish
, but for now: tar -tf $(npm pack)
will list out files that will be published given your current configuration. Note that you'll end up with a tgz
file in the root of your local repository, so be sure to delete it afterward!
Conclusion
Once you're all set up you can pat yourself on the back for leaving out a bunch of extraneous noise in your repo's history. You can also rest easy knowing that consumers of your library have the option to either compile it themselves, or be acceptably lazy and use precompiled (and consistently generated!) code.