[node.js] How to use executables from a package installed locally in node_modules?

How do I use a local version of a module in node.js. For example, in my app, I installed coffee-script:

npm install coffee-script

This installs it in ./node_modules and the coffee command is in ./node_modules/.bin/coffee. Is there a way to run this command when I'm in my project's main folder? I guess I'm looking for something similar to bundle exec in bundler. Basically, I'd like to specify a version of coffee-script that everyone involved with the project should use.

I know I can add the -g flag to install it globally so coffee works fine anywhere, but what if I wanted to have different versions of coffee per project?

This question is related to node.js coffeescript npm node-modules

The answer is


I prefer to not rely on shell aliases or another package.

Adding a simple line to scripts section of your package.json, you can run local npm commands like

npm run webpack

package.json

{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "webpack": "webpack"
  },
  "devDependencies": {
    "webpack": "^4.1.1",
    "webpack-cli": "^2.0.11"
  }
}

For Windows use this:

/* cmd into "node_modules" folder */
"%CD%\.bin\grunt" --version

I am a Windows user and this is what worked for me:

// First set some variable - i.e. replace is with "xo"
D:\project\root> set xo="./node_modules/.bin/"

// Next, work with it
D:\project\root> %xo%/bower install

Good Luck.


update: If you're on the recent npm (version >5.2)

You can use:

npx <command>

npx looks for command in .bin directory of your node_modules

old answer:

For Windows

Store the following in a file called npm-exec.bat and add it to your %PATH%

@echo off
set cmd="npm bin"
FOR /F "tokens=*" %%i IN (' %cmd% ') DO SET modules=%%i
"%modules%"\%*

Usage

Then you can use it like npm-exec <command> <arg0> <arg1> ...

For example

To execute wdio installed in local node_modules directory, do:

npm-exec wdio wdio.conf.js

i.e. it will run .\node_modules\.bin\wdio wdio.conf.js


Use npm run[-script] <script name>

After using npm to install the bin package to your local ./node_modules directory, modify package.json to add <script name> like this:

$ npm install --save learnyounode
$ edit packages.json
>>> in packages.json
...
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "learnyounode": "learnyounode"
},
...
$ npm run learnyounode

It would be nice if npm install had a --add-script option or something or if npm run would work without adding to the scripts block.


I encountered the same problem and I don't particularly like using aliases (as regular's suggested), and if you don't like them too then here's another workaround that I use, you first have to create a tiny executable bash script, say setenv.sh:

#!/bin/sh

# Add your local node_modules bin to the path
export PATH="$(npm bin):$PATH"

# execute the rest of the command
exec "$@"

and then you can then use any executables in your local /bin using this command:

./setenv.sh <command>
./setenv.sh 6to5-node server.js
./setenv.sh grunt

If you're using scripts in package.json then:

...,
scripts: {
    'start': './setenv.sh <command>'
}

Use npm-run.

From the readme:

npm-run

Find & run local executables from node_modules

Any executable available to an npm lifecycle script is available to npm-run.

Usage

$ npm install mocha # mocha installed in ./node_modules
$ npm-run mocha test/* # uses locally installed mocha executable 

Installation

$ npm install -g npm-run

If you want to keep npm, then npx should do what you need.


If switching to yarn (a npm replacement by facebook) is an option for you, then you can call:

 yarn yourCmd

scripts inside the package.json will take precedence, if none is found it will look inside the ./node_modules/.bin/ folder.

It also outputs what it ran:

$ yarn tsc
yarn tsc v0.27.5
$ "/home/philipp/rate-pipeline/node_modules/.bin/tsc"

So you don't have to setup scripts for each command in your package.json.


If you had a script defined at .scripts inside your package.json:

"tsc": "tsc" // each command defined in the scripts will be executed from `./node_modules/.bin/` first

yarn tsc would be equivalent to yarn run tsc or npm run tsc:

 yarn tsc
 yarn tsc v0.27.5
 $ tsc

Include coffee-script in package.json with the specific version required in each project, typically like this:

"dependencies":{
  "coffee-script": ">= 1.2.0"

Then run npm install to install dependencies in each project. This will install the specified version of coffee-script which will be accessible locally to each project.


Add this script to your .bashrc. Then you can call coffee or anyhting locally. This is handy for your laptop, but don't use it on your server.

DEFAULT_PATH=$PATH;

add_local_node_modules_to_path(){
  NODE_MODULES='./node_modules/.bin';
  if [ -d $NODE_MODULES ]; then
    PATH=$DEFAULT_PATH:$NODE_MODULES;
  else
    PATH=$DEFAULT_PATH;
  fi
}

cd () {
  builtin cd "$@";
  add_local_node_modules_to_path;
}

add_local_node_modules_to_path;

note: this script makes aliase of cd command, and after each call of cd it checks node_modules/.bin and add it to your $PATH.

note2: you can change the third line to NODE_MODULES=$(npm bin);. But that would make cd command too slow.


Nice example

You don't have to manipulate $PATH anymore!

From [email protected], npm ships with npx package which lets you run commands from a local node_modules/.bin or from a central cache.

Simply run:

$ npx [options] <command>[@version] [command-arg]...

By default, npx will check whether <command> exists in $PATH, or in the local project binaries, and execute that.

Calling npx <command> when <command> isn't already in your $PATH will automatically install a package with that name from the NPM registry for you, and invoke it. When it's done, the installed package won’t be anywhere in your globals, so you won’t have to worry about pollution in the long-term. You can prevent this behaviour by providing --no-install option.

For npm < 5.2.0, you can install npx package manually by running the following command:

$ npm install -g npx

Same @regular 's accepted solution, but Fish shell flavour

if not contains (npm bin) $PATH
    set PATH (npm bin) $PATH
end

I've always used the same approach as @guneysus to solve this problem, which is creating a script in the package.json file and use it running npm run script-name.

However, in the recent months I've been using npx and I love it.

For example, I downloaded an Angular project and I didn't want to install the Angular CLI globally. So, with npx installed, instead of using the global angular cli command (if I had installed it) like this:

ng serve

I can do this from the console:

npx ng serve

Here's an article I wrote about NPX and that goes deeper into it.


You can also use direnv and change the $PATH variable only in your working folder.

$ cat .envrc
> export PATH=$(npm bin):$PATH

In case you are using fish shell and do not want to add to $path for security reason. We can add the below function to run local node executables.

### run executables in node_module/.bin directory
function n 
  set -l npmbin (npm bin)   
  set -l argvCount (count $argv)
  switch $argvCount
    case 0
      echo please specify the local node executable as 1st argument
    case 1
      # for one argument, we can eval directly 
      eval $npmbin/$argv
    case '*'
      set --local executable $argv[1]
      # for 2 or more arguments we cannot append directly after the $npmbin/ since the fish will apply each array element after the the start string: $npmbin/arg1 $npmbin/arg2... 
      # This is just how fish interoperate array. 
      set --erase argv[1]
      eval $npmbin/$executable $argv 
  end
end

Now you can run thing like:

n coffee

or more arguments like:

n browser-sync --version

Note, if you are bash user, then @Bob9630 answers is the way to go by leveraging bash's $@, which is not available in fishshell.


If you want your PATH variable to correctly update based on your current working directory, add this to the end of your .bashrc-equivalent (or after anything that defines PATH):

__OLD_PATH=$PATH
function updatePATHForNPM() {
  export PATH=$(npm bin):$__OLD_PATH
}

function node-mode() {
  PROMPT_COMMAND=updatePATHForNPM
}

function node-mode-off() {
  unset PROMPT_COMMAND
  PATH=$__OLD_PATH
}

# Uncomment to enable node-mode by default:
# node-mode

This may add a short delay every time the bash prompt gets rendered (depending on the size of your project, most likely), so it's disabled by default.

You can enable and disable it within your terminal by running node-mode and node-mode-off, respectively.


Use the npm bin command to get the node modules /bin directory of your project

$ $(npm bin)/<binary-name> [args]

e.g.

$ $(npm bin)/bower install

Update: I no longer recommend this method, both for the mentioned security reasons and not the least the newer npm bin command. Original answer below:

As you have found out, any locally installed binaries are in ./node_modules/.bin. In order to always run binaries in this directory rather than globally available binaries, if present, I suggest you put ./node_modules/.bin first in your path:

export PATH="./node_modules/.bin:$PATH"

If you put this in your ~/.profile, coffee will always be ./node_modules/.bin/coffee if available, otherwise /usr/local/bin/coffee (or whatever prefix you are installing node modules under).


The PATH solution has the issue that if $(npm bin) is placed in your .profile/.bashrc/etc it is evaluated once and is forever set to whichever directory the path was first evaluated in. If instead you modify the current path then every time you run the script your path will grow.

To get around these issues, I create a function and used that. It doesn't modify your environment and is simple to use:

function npm-exec {
   $(npm bin)/$@  
}

This can then be used like this without making any changes to your environment:

npm-exec r.js <args>

I'd love to know if this is an insecure/bad idea, but after thinking about it a bit I don't see an issue here:

Modifying Linus's insecure solution to add it to the end, using npm bin to find the directory, and making the script only call npm bin when a package.json is present in a parent (for speed), this is what I came up with for zsh:

find-up () {
  path=$(pwd)
  while [[ "$path" != "" && ! -e "$path/$1" ]]; do
    path=${path%/*}
  done
  echo "$path"
}

precmd() {
  if [ "$(find-up package.json)" != "" ]; then
    new_bin=$(npm bin)
    if [ "$NODE_MODULES_PATH" != "$new_bin" ]; then
      export PATH=${PATH%:$NODE_MODULES_PATH}:$new_bin
      export NODE_MODULES_PATH=$new_bin
    fi
  else
    if [ "$NODE_MODULES_PATH" != "" ]; then
      export PATH=${PATH%:$NODE_MODULES_PATH}
      export NODE_MODULES_PATH=""
    fi
  fi
}

For bash, instead of using the precmd hook, you can use the $PROMPT_COMMAND variable (I haven't tested this but you get the idea):

__add-node-to-path() {
  if [ "$(find-up package.json)" != "" ]; then
    new_bin=$(npm bin)
    if [ "$NODE_MODULES_PATH" != "$new_bin" ]; then
      export PATH=${PATH%:$NODE_MODULES_PATH}:$new_bin
      export NODE_MODULES_PATH=$new_bin
    fi
  else
    if [ "$NODE_MODULES_PATH" != "" ]; then
      export PATH=${PATH%:$NODE_MODULES_PATH}
      export NODE_MODULES_PATH=""
    fi
  fi   
}

export PROMPT_COMMAND="__add-node-to-path"

zxc is like "bundle exec" for nodejs. It is similar to using PATH=$(npm bin):$PATH:

$ npm install -g zxc
$ npm install gulp
$ zxc which gulp
/home/nathan/code/project1/node_modules/.bin/gulp

Examples related to node.js

Hide Signs that Meteor.js was Used Querying date field in MongoDB with Mongoose SyntaxError: Cannot use import statement outside a module Server Discovery And Monitoring engine is deprecated How to fix ReferenceError: primordials is not defined in node UnhandledPromiseRejectionWarning: This error originated either by throwing inside of an async function without a catch block dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.62.dylib error running php after installing node with brew on Mac internal/modules/cjs/loader.js:582 throw err DeprecationWarning: Buffer() is deprecated due to security and usability issues when I move my script to another server Please run `npm cache clean`

Examples related to coffeescript

React onClick and preventDefault() link refresh/redirect? How to run Gulp tasks sequentially one after the other How do I get the Back Button to work with an AngularJS ui-router state machine? How does Trello access the user's clipboard? Create an ISO date object in javascript How to rotate a 3D object on axis three.js? Exec : display stdout "live" Ternary operation in CoffeeScript How to use executables from a package installed locally in node_modules? Why doesn't adding CORS headers to an OPTIONS route allow browsers to access my API?

Examples related to npm

What does 'x packages are looking for funding' mean when running `npm install`? error: This is probably not a problem with npm. There is likely additional logging output above Module not found: Error: Can't resolve 'core-js/es6' Browserslist: caniuse-lite is outdated. Please run next command `npm update caniuse-lite browserslist` ERROR in The Angular Compiler requires TypeScript >=3.1.1 and <3.2.0 but 3.2.1 was found instead DeprecationWarning: Buffer() is deprecated due to security and usability issues when I move my script to another server Please run `npm cache clean` What exactly is the 'react-scripts start' command? On npm install: Unhandled rejection Error: EACCES: permission denied Difference between npx and npm?

Examples related to node-modules

SyntaxError: Cannot use import statement outside a module What could cause an error related to npm not being able to find a file? No contents in my node_modules subfolder. Why is that? Node - was compiled against a different Node.js version using NODE_MODULE_VERSION 51 npm WARN enoent ENOENT: no such file or directory, open 'C:\Users\Nuwanst\package.json' Cannot find the '@angular/common/http' module How to use paths in tsconfig.json? Could not find a declaration file for module 'module-name'. '/path/to/module-name.js' implicitly has an 'any' type How to fix 'fs: re-evaluating native module sources is not supported' - graceful-fs How to provide a mysql database connection in single file in nodejs Git - Ignore node_modules folder everywhere