How to publish a npm package (properly)
To begin, set up an npm project in the usual way.
Write a module that you want to publish, probably using the ES6 export
command to export things from it. You should also write some tests to test it; you could use a unit test framework such as jasmine, but the simplertime
example package doesn’t.
Example
For Part 5 I made a simple time parser/formatter; now I’ve published it as the simplertime
npm package. See the repo to see how it is set up, and try npm install simplertime
to download the installed package to find out how it differs from the version published in the repo (in short: the tests are gone and package.json was modified by npm).
Main preparation steps (for JavaScript and TypeScript)
- Create a readme.md file with documentation for your package. This file will be rendered on npmjs.com.
- It is also recommended to create documentation to describe the functions and classes in your code, in JSDoc format.
-
If your package can be invoked from the command line, then in package.json, create a section called
"bin"
for it. For example, if you have a command calledfoo
implemented indist/foo.js
, you’ll need a section like this:"bin": { "foo": "dist/foo.js" }
npm has a shebang requirement: the first line of your JavaScript command file must be this:
#!/usr/bin/env node
-
Check npmjs.com to find out if the package name you want is already in use.
New package names very similar to existing package names are not always allowed, and currently there is no way to be sure if the name you want to use is available before you publish.
-
In package.json, set the
"main"
option to the module’s JavaScript name, e.g."main": "index.js"
. This option is needed for users to be able to import a module likeimport * from 'simplertime'
.'simplertime'
refers to a folder name in node_modules, andmain
controls which file within the folder is loaded. -
You can either (1) specify the files you want in your package using the
"files"
option of package.json, or (2) specify the files you don’t want in your package by creating an .npmignore file (if there is no .npmignore file,npm
will look for.gitignore
instead.) As an example, thefiles
list forsimplertime
is"files": ["readme.md", "**/simplertime.*"]
Notice that package.json itself is included automatically. As far as I can tell, the .npmignore file is ignored when you are using
"files"
. And by the way: Windows has had a bug for over 20 years, due to which you can’t create .npmignore (or any text file whose name starts with a dot) in Windows Explorer, so use another program such as VS Code.I strongly recommend using the
"files"
option to avoid accidentally putting temporary or “junk” files in your package. For example, usingnpm pack
to preview your package will produce a tgz file with the contents of your package, and it would be easy to include such tgz files in your package accidentally.npm will not warn you if a file listed in
files
is missing. - If you’re a perfectionist with OCD, review all package.json fields
- In package.json, check your dependencies. Make sure that the
"dependencies"
section contains all the dependencies that are required by people using your package, but only those dependencies. For example if you see"typescript"
there, that’s wrong, TypeScript should be in the"devDependencies"
section instead. Other examples of packages that should usually be in"devDependencies"
includewebpack
,uglifyjs
,jasmine
,mocha
,jest
, andts-node
. If you find a dependency in the wrong section, you can move it by hand (be careful with those commas) or usenpm install --save-dev package-name
to movepackage-name
to thedevDependencies
section (but this seems to initiate a new package download).
If it’s a TypeScript package…
When publishing TypeScript packages on npm
, it’s polite to publish code that has been compiled to JavaScript so that people who are not using TypeScript can still use your package. By also including a .d.ts file (which contains type information), you don’t even need to publish the original TypeScript source code.
- For compatibility with both web servers and web browsers, use the
"module": "umd"
option in your tsconfig.json (see Part 3 for a sample tsconfig.json file.) - In package.json in the
"scripts"
section, create a build command for creating the Javascript files from the TypeScript code, e.g."build": "tsc"
. Usenpm run build
to run it. - (optional) A popular thing to do in npm packages is to place the code in a folder called dist. In tsconfig.json you can send output there using
"outDir": "dist"
. If you get the error “Cannot write file ‘…/dist/….d.ts’ because it would overwrite input file”, then outside the compiler options, you must add"dist"
to the"exclude"
list (see example). And don’t forget to adddist/
to your"main"
option. - When end-users import the published module, I have found that VS Code will detect your typing files automatically (e.g. if
"main"
isdist/index.js
then VS Code findsdist/index.d.ts
), buttsc
will not. Therefore, before you publish, you need an extra option in package.json beside yourmain
option, such as"typings": "dist/index"
to telltsc
where the d.ts file is. Also, should not include the.d.ts
extension in the"typings"
option, or you might get a strange error: “Cannot write file ‘[your-module].d.ts’ because it would overwrite input file”.
How to minify your code (optional)
Some npm packages offer minified or production versions, but this is not required; there is no standardized method in npm to offer separate “development” and “production” versions of your package, and developers using Webpack get their entire app minified (including npm packages) when they use webpack -p
or the --optimize-minimize
option. Still, some developers will appreciate having a minified version that they can refer to, e.g. via aliases (Webpack aliases or Parcel aliases.
TypeScript aliases don’t work for this purpose because if you tell TypeScript that A
is an alias for B
, then TypeScript loads B
for type-checking purposes, but it generates code that still refers to A
.
So, I made a brief page explaining how to minify your code.
How to verify that your package is set up correctly
It’s important to check, because you cannot change a package after publishing it (except after changing the version number to something new that you’ve never used before). And if you don’t check, you won’t know if the published version of your package actually works. So clearly, the smart policy is to check in advance.
But how? npmjs.com offers no good answer to this question.
Option 1: npm pack
You can run npm pack
to get a preview of what your package will contain:
PS C:\Dev\simplertime> npm pack
npm notice
npm notice package: simplertime@1.0.0
npm notice === Tarball Contents ===
npm notice 950B package.json
npm notice 1.1kB readme.md
npm notice 3.8kB dist/simplertime.d.ts
npm notice 6.3kB dist/simplertime.js
npm ...
npm notice === Tarball Details ===
npm notice name: simplertime
npm notice version: 1.0.0
npm notice filename: simplertime-1.0.0.tgz
npm notice package size: 6.2 kB
npm notice unpacked size: 24.2 kB
npm notice shasum: e731092eea4a4e49be9912e4710348f41c2c9dc4
npm notice integrity: sha512-6fxbApL17Dol9[...]WGtw70i6xv4mg==
npm notice total files: 7
From this you can see whether the intended files were included, but it doesn’t tell you if other settings are correct (e.g. main
, typings
, devDependencies
).
You can improve this a bit by building and running unit tests at the same time. npm
runs the prepare
script before npm pack
and before npm publish
, so you’d want something like this in package.json:
"scripts": {
...
"prepare": "npm run build && npm test"
}
Option 2 (recommended): use testpack-cli
I think the right way to ensure that your package is packaged correctly is to run your unit tests against it - the same tests that make sure your code works locally could also test the packaged code.
I was trying to write a custom script in my project that would automate this idea, but I hit a roadblock: the way module resolution works. Specifically, when you import a local code file you must import from ./
, e.g. import {stuff} from "./yourModule"
, but when you import something from node_modules you must import from your_package_name
. So even if your package has the same name as your module (e.g. simplertime
has simplertime.ts
), a single import command cannot work for both. Therefore, there’s no obvious way to write your tests in TypeScript so that they can run against either the local copy or a packaged version in node_modules
.
I decided to solve this problem in a way that I hoped would help the whole community: by making a testing tool to test your package before publishing. It’s called testpack-cli
. I tried other names, but names like testpack
, testpackage
, packtest
, packagetest
, checkpack
, and checkpackage
were already taken by random people publishing “test packages” to “check” if they could figure out how to use npm. Finally I settled on the name npm-testpack
which seemed unused, but it turned out that someone had already taken npm-test-pack
, which blocked me.
You use it like this:
- In a terminal:
npm install --save-dev --global testpack-cli
- Run it with
testpack
- Read the documentation if you need to configure it
Here’s what it does: it packs your package with npm pack
, then creates a test folder with its own separate package.json
file. In the test folder it installs your package, it installs your unit test framework (if you one of the common ones is listed as a dependency), and it adds your unit tests (identified by the test patterns test* *test.* *tests.* *test*/**
). If you have a tsconfig.json file, that’s copied to the test folder too. And finally, whenever your tests import "./something"
or "../src/something"
, the ./
or ../src/
prefix is stripped off.
Therefore, if your package is called pkg, you should ideally create a single “main” source file called pkg.js or pkg.ts which you import using import {...} from "./P"
in your tests. The test folder’s copy will import "P"
instead.
By default, the test folder is created as a sibling: if your package name is pkg
then the test folder is ../pkg-testpack
.
If your package is a TypeScript project, make sure that your tests in the test folder are importing the compiled JavaScript version, because your end-users might not be using TypeScript. One way to guarantee this is to exclude all .ts files from your package (if you do that, be sure source maps are disabled in your tsconfig.json.) Your tests should still be written in TypeScript to make sure your d.ts files work; use "declaration": true
in tsconfig.json so they can access type information.
How to publish (finally!)
- Create an account on npmjs.com. You’ll be asked for your full name, username and email address.
- You must use
npm
to publish your package. Log in with thenpm adduser
terminal command:
> npm adduser
Username: qwertie
Password:
Email: (this IS public) qwertie256@gmail.com
Logged in as qwertie on https://registry.npmjs.org/.
- In a terminal, run
npm publish
and you’re done! By default your package will have thelatest
tag, which means that’s the package that will be used when people typenpm install your-package
. - View your package on npmjs.com by typing its name in the search box (it may be hidden at first.)
To update the package (publish a new version)
- In package.json, increase the version number. Be aware that npm uses semantic versioning rules.
- Verify that your package is set up correctly (see above).
- In a terminal, run
npm publish
again if your code is stable, ornpm publish --tag beta
if not (a side effect of adding a custom tag likebeta
is to subtract thelatest
tag, so that the version you are publishing will not be installed automatically bynpm install your-package
.)