OnPoint Plugins

  • Plugins
    • Advanced Sidebar Menu
    • Go Live Update Urls
  • About
    • Services
    • Showcase
  • Blog
  • Contact
  • Support
  • Log In

Node As A CLI

This is an exhaustive article on creating command line run scripts/executables with node and how it all works. It is broken into sections for convenience if you are looking for a specific topic. Otherwise, grab a coffee and prepare to learn more than you ever wanted to know about node cli.

For the purpose of this article the words package and project are interchangeable and both mean a node app which is defined by the package.json standard.

For the purposes of this article all project JavaScript will live inside the ./src directory. This is required for some of the techniques.

The bin package.json declaration:

All node cli executables start with a declaration within the projects package.json file called bin. These may be declared in two formats.

JSON

This will use the name of the package test-app as the command name and run the specified file ./src/index.js as the command.

JSON

This will install two commands, test-app-index and test-app-another and run the specified file as each command. You may name the commands whatever you like and have as many as you would like. Because they will be potentially installed globally, they should be namespaced or at least something unique.

The files which are run may be .js but they don’t have to be. They could be any type of executable your system knows how to handle. For instance, they could be php or bash if you like. You determine the type of file by adding a shebang declaration to the top of the file like so:

Be sure you have your shebang line setup in that format !/usr/bin/env then then the program that handles it. This way it will work whether you are on windows, linux, or mac.

If you are running your project’s index file you will want to use this shebang:

If you are working on the project locally and want to be able to use the bin command(s) you now run this command to install them:

Shell

If you are using yarn an alternative which will keep things cleaner would be:

Shell

You may now open your terminal and type in your specified command (test-app if you are using the first above example or test-app-index and/or test-app-another if you are using the second above example) and your script is run.

How node handles bin declarations:

If you are working on your project locally you may have noticed that you can run npm install or yarn install and you can’t run your bin command from anywhere. Until you run npm link your bin declaration appears to do nothing.

Node only cares about bin declarations when a package is installed as a dependency or when you run npm link.

Packages installed as dependencies with bin declarations

When a package is installed as dependency during npm install, node looks for the bin declaration and generates executables based on the shebang line in the file(s) pointed to by bin. For instance if you use the first above example, node will generate a test-app and a test-app.cmd file. Both files do the same thing but one is for windows and one for mac/linux.

The content of the executable test-app will look something like this:

Shell

The content of the executable test-app.cmd will look something like this:

Shell

Notice how these executables call node before the command. This is assuming the file pointed to via bin had the !/usr/bin/env node shebang line. If the shebang line was pointed to bash or something else, these executables would be calling bash instead of node.

These files will be generated within your project’s node_modules/.bin directory.

You have probably noticed that even though these executables have been generated, you can’t call them from the command line unless you call them with a full path to the directory like:

Shell

Local projects with bin declarations using npm link

When you run either npm link or yarn link on a local project, node looks for the bin declaration and generates executables based on the shebang line in the file(s) pointed to by bin. This works the same way as node handles dependencies with one important difference.

Node generates the executables inside it’s main PATH linked directory.

This directory will be located in a different location depending on your system and if you are using nvm. The location will also be different depending on whether you use yarn link or npm link.

If you would like to know where this file is generated so you can inspect it, run on of the following commands:

Shell
Shell

An important takeaway here is node does not create symbolic links to the files that are declared in bin but rather generates executables which call the files.

Since these new executables are generated within the PATH they are now accessible via any terminal may be run by simply typing their name such as test-app.

Bonus: To use your local project inside another project simply run yarn link test-app in another project and it will use your local project instead of one installed from npm. This works via an automatically generated symbolic link.

Accessing the executables generated in the node_modules/.bin directory.

Because you installed a package as a dependency, you probably want to be able to call the executables the package has declared. There are a couple of ways to do this.

Install a package globally.

Any package that you may install as a dependency may be installed globally. This may be done by adding a -g flag to the npm install command like so:

Shell

This will tell node to add the package to it’s global location and generate the
executables inside it’s main PATH linked directory. You will now be able to call these command from any terminal simply by typing their name.

While global packages are very useful for making command line utilities available such as grunt or npx, they do saturate the global space and should be installed sparingly.

When installing global packages, it’s recommended to stick with one package handler for all globals so you don’t end up with conflicts. With this in mind, I recommend using yarn for local installs and npm for globals.

Using package.json declared scripts.

When you declare and run scripts within your package.json file, you have access to all the executables which exist in the .bin directory. For example let’s say we have grunt as a dependency. We can then declare a script called run_grunt which calls grunt:

JSON

Using npm’s run-script command we can now call the grunt executable which exists in node_modules/.bin/grunt.

Shell

Any combination of command or arguments you can imagine may be declared within scripts. They also have access to your full PATH so feel free to use any global commands you have here as well.

Using the npx package

There is a widely used package called npx which lets you call executables found within the .bin directory.

Npx will also auto install packages if they don’t exist before calling the executables but it’s more predictable to install packages like normal before calling npx.

Because npx is intended as a command line utility, it makes sense to install it globally.

Shell

Now running any executable within .bin is as simple as calling npx <executable>. Using the grunt example again:

Shell

Calling your script directly.

There are a lot of cases where you write a node script and don’t necessarily want it installed as a global executable or are not actually going to publish it. Sometimes your scripts are one-time use and you just need a quick way to write a script in a language you are used to using.

Luckily node may be used as is for calling .js scripts:

Shell

If you want, you can also create bash or batch scripts which call your .js script and/or pass different arguments like so:

Shell

Using ES6 with node scripts.

I’m sure by now you are used to using ES6 within your JS and want to keep on using it with node scripts. The most common way to use ES6 with JS is to compile it with babel. Here are a couple of ways to make babel work with node scripts.

Note: Recent versions of Babel now require you to import packages separately for different features. For example if you want to use import you will need to add babel-preset-es2015 as a dependency and {"presets": ["es2015"]} within a “.babelrc” file for it to work.

Using the babel-register package.

Babel has a package called babel-register which places babel in front of your JS and automatically compiles it with babel before sending it to node.

Install it like any other local dependency:

Shell

At the top of your ./src/index.js file, require babel-register. Then use a standard require syntax to load the rest of your project through this file.

JS

Now any of the above methods will work with your project using ES6.

Using the babel-node command utility

Babel has a package called babel-cli which contains a bin script called babel-node. You may either install babel-cli as a global package or a local dependency as shown in above examples.

Installed Globally

If you installed babel-cli as a global package you can simply call:

Shell

If your script is going to be declared as a bin you may now change the shebang line to use babel-node and the correct executable will be pointed to when you run npm link.

Your shebang line will look like this:

If you are going to be distributing your project as a package, be sure to add babel-cli as a dependency.

Installed Locally

If you installed babel-cli as a local dependency you may use any of the methods mentioned above in “Accessing the executables generated in the node_modules/.bin directory.” Like so:

Using package.json declared scripts:

JSON

Using the npx package:

Shell

Create a bash script:

Shell

Generating a stand along executable.

Sometimes you want to be able to distribute your project without forcing users to install node, install dependencies, and run a special command. Sometimes you just want to pass them a .exe file and let them run it.

Luckily there is a package for doing just so called pkg. Install it like normal either locally or globally.

By now I’m sure you understand the difference between local and global and how to use each so I won’t add more examples.

Generating your executables is as simple as running this command:

Shell

This will generate exectuables for windows, mac, and linux. If you want to specify a particular OS or maybe a program name, pkg supports arguments for these types of things:

Shell

Using ES6.

You will notice that you can’t call babel-node with pkg and using the babel-register method above still gives you a bunch of errors when you try to run pkg.

This is because pkg is strictly node based, it compiles your JS files directly and not like a typically rendered app. You can solve this problem by compiling your files first with babel and then running the compiled files through pkg.

Because you don’t want to type a bunch of commands every time you want to compile, I recommend registering a package.json script to do this for you:

JSON

This script declares the following processes:

  1. Get all the files in the ./src directory.
  2. Compile them with babel.
  3. Put all compiled files in the ./dist.
  4. Generate the executables based off ./dist/index.js.

You may add additional arguments to this script if desired like so:

Shell

In Closing

That’s really I’ll I’ve got to say about that.

Filed Under: Programming, Tutorials

« Responsive Image Srcset Sizes
Advanced Sidebar Menu – Version 3.7.0 »

Find Something

Categories

Follow Us

Subscribe to get the latest news and updates.

Follow

© 2023 · WordPress Development · Terms Of Service · Privacy Policy · Cookie Policy · Log in

  • Plugins
  • Blog
  • Contact