Skip to content

Files & Ignores

Reggi edited this page May 23, 2024 · 4 revisions

Introduction

Most npm users tend to simply publish their modules, and expect the appropriate files to be included, but did you know npm allows more fine-grained control over what you end up pulling in?

This wiki page documents a lot of the details around these mechanisms, hopes to clarify its corner cases, and points to specific issues related to what users would expect, or we would want, from these cases.

There is also a detailed test suite covering most of the behavior discussed in this page.

files

The "files" array in package.json is an optional field. When present, it should be an array of entries to be included when publishing your package. It looks something like this:

// package.json
{
  "files": [
    "foo.js",
    "lib/"
  ]
}

In this example, foo.js in the root directory will be included when you do npm publish. So will lib/ and all its contents (recursively).

If the files array is omitted, everything except automatically-excluded files will be included in your publish.

Details

  • Entries in files are minimatch globs. That means *.js, lib/**/*.js, etc, all work.
  • Entries in files are converted to include subdirectories, even ones intended as files. For example, foo.js will be treated as both foo.js and foo.js/**. It also means lib is all you need in order to include everything in that directory.
  • A trailing / in files does nothing.
  • npm automatically includes and excludes certain files, regardless of your settings. The entire list is in the npm documentation for package.json.
  • node_modules/ gets special treatment. If you want to include dependencies in your publish, use bundledDependencies.
  • "The consequences are undefined" if you try to negate any of the files entries (that is, "!foo.js"). Please don't. Use .npmignore.

npmignore

You can use ignore files, optionally in combination with a files array, in order to get more fine-tuned control over what gets included or excluded. These are intended to have identical syntax to gitignore, and will be used when deciding what to include in the published npm package.

# .npmignore
/test
.idea

There are various issues with inconsistent or unexpected behavior around ignores. See the Known Issues section below.

Details

  • If there is a .gitignore file, and .npmignore is missing, .gitignore's contents will be used instead: That means .npmignore trumps .gitignore: They are not merged. Furthermore, .gitignore will be renamed to .npmignore in the resulting tarball.
  • Entries matched by files will be included, regardless of .npmignore settings. This is also a known issue.
  • You can have nested .npmignore files in your project. They will match starting from their current directory.
  • Lines prefixed with / will only match entries starting from the current location of that .npmignore. For example, /foo will match foo and not lib/foo, but foo will match both.
  • ./ is invalid syntax for both gitignore and npmignore if you're intending to match starting from the current directory. Use / instead.
  • npmignore syntax is otherwise identical to the files syntax, for ignoring: that means globs, automatic subdirs, etc, all work the same way.

Known issues

  • #11669 - .npmignore entries should trump entries in the files array
  • #5673 - There is no user-level or global npmignore.
  • #8510 - main file should be included automatically, regardless of files setting.
  • #8791 - npm does not warn if files explicitly referenced in files do not exist.
  • #3968 - npm doesn't respect .gitignore if ignored folder has an index.js in it.

Note: Several of the above already have partial fixes written. Wanna help? Check in on the issue, and you can help get them over the finish line! There's a Files and Ignores milestone to track these issues, which is the most up-to-date source of info.

Implementation

Files and ignores are currently implemented with a stack of fstream-based libraries. There is sometimes a bit of confusion between what is responsible for what, so here's an attempt to document the role different modules have. Note that the standard mechanism for extending fstream is through subclassing and overriding, and all of these modules are (chained) subclasses of fstream.DirReader.

  • lib/utils/tar.js is the main npm-side entry point: This contains the tarball-generation code used by npm pack (and by extension, npm install and npm publish). There is relatively little business logic here, but this is the right layer for things such as warnings, errors, and actual tarball packing/unpacking. It uses a subclass of fstream-npm.
  • fstream-npm contains most of the npm-specific business logic related to file inclusion and exclusion. This includes support for reading and handling the files array, bundledDependencies, reading .npmignore as an ignore file, and automatic inclusions/exclusions like README and LICENSE. It uses a subclass of fstream-ignore.
  • fstream-ignore is a generic directory reader that looks for .gitignore-style ignore files and applies similar logic to the file stream that git does. It includes no npm-specific business logic, and handles all the logic of reading the ignore files, parsing them, and recursively reading directories while looking for more ignore files and applying them. It is a subclass of fstream.DirReader.
  • fstream is a library with a suite of stream readers for handling various file I/O operations. The DirReader class included in this library is the base for all file inclusion in this stack.

Note that there are a few inconsistencies above: Most notably that a lot of business logic is divided between tar.js and fstream-npm with no obvious guidelines -- this happened for historical reasons. There was also a lot of duplication for practical purposes, which is removed by a PR (#11995) that fixes an issue related to unintentional dependency inclusion.