Publish my first npm package

I published my first npm package today. It's a micro-css framework, based on tachyons.css, but extended with Algolia-specific classes. What the package actually do is not the point of this post, though.

What I'd like to share here are the tricks and workarounds I had to get right to publish the final package on NPM.

Having a script ready for release

I've reused a release script we've been using at Algolia on one of our JavaScript project. It already handles all the nice dance of updating the develop branch, switching to master, merging, updating the version number and publishing to npm.

I've extended it a bit to add an actual build step, as I have to compile SCSS files to final CSS (including a minified version).

Not using yarn, but good old npm

Main problem I had was that I could run my ./scripts/release script from the command line and have everything uploaded, but if I ever did the same through yarn run release, it would fail on the actual publish phase.

After some digging I found than yarn cannot read the npm credentials files needed to publish. The solution is to run npm run instead of yarn run.

So I added a check at the start of my ./scripts/release script to stop execution if the script is not run from the context of npm. Turns out you can do that by reading the process.env._ variable that contains the path to the binary that is executing the script.

if (!process.env._.match(/npm$/)) {
  shell.echo('This script must be run with "npm run release"'.error);
  shell.echo('It will not correctly publish to NPM if used with yarn');
  process.exit(1);
}

That way, I won't forget that I need to run it through npm and not yarn.

Publishing needed files

Once published, I tried to install it as a dependency in another project and realized I was publishing unneeded files. I don't need to publish my ./script repository for example.

My package is also based on tachyons, so I include the SCSS source file of tachyons in my own SCSS file to build the final CSS file. But I don't want to publish all the sources files of tachyons when you're pulling my dependencies.

So I started fiddling with the files key of package.json. You're supposed to put in here and array of all the filepaths (as globs) you'd like to include in your final published package. You can also define a .npmignore file (with the exact same syntax as a .gitignore file) that is used to exclude some files from being publish.

The sneaky part here is that the files key has precedence over the .npmignore file. And in my case I wanted to include the ./scr directory, but exclude the ./src/vendors directory. No luck there for me.

In the end I had to completly ditch the files and use .npmignore. So instead of defining a list of files to include and then exclude some specific files, I had to define a list of all the files/directories I wanted to exclude. Not as easy, but it works and I now have the built .css files as well as my source .scss files in the final package.

Fighting with the postinstall script

Now, the last bit was a bit more tricky. I use a postinstall script in my package.json to automatically copy the tachyons.scss source files to ./src/vendors (the directory I want excluded from the previous step) from my node_modules.

That way I don't have to commit tachyons.css files to my repo, but still have it referenced in package.json, and pinned to a specific version. It keeps my dependencies clear, and my repo lightweight.

The thing is, the postinstall hook is called both when you're manually running yarn install locally, but also when your dependency is installed by someone else. That was a big surprise for me. I could imagine that was the case at first, for all the security implications this might cause (anyone could run an arbitrary script as part of the postinstall hook of any deep depenency you have in your project).

Still, I didn't want that to happen in my case because I actually excluded the ./scripts/postinstall script from my build, so the postinstall hook was failing, and the whole installation of my package was failing.

After more than 10 releases to test it, I settled on a trivial solution. At first I tried to check if the postinstall hook was triggered as part of a local or dependency install but could not find any reliable way to test it.

Instead, I checked if the ./scripts/postinstall file was present, and run it if so. Because I was excluding my ./scripts folder from the published packaged, the whole hook was not run when installed as a dependency.

Here is the final postinstall hook I used (the true is needed so the hook actually succeed for the install to complete).

{
  "scripts": {
    "postinstall": "(test -f ./scripts/postinstall && ./scripts/postinstall) || true"
  }
}

Tags : #npm, #yarn, #tachyons, #css

Want to add something ? Feel free to get in touch on Twitter : @pixelastic